from bithustler_client.client.client import BithustlerClient
from bitquote_client.client.client import BitquoteClient
from bittrade_client.client.client import BittradeClient
from bittrade_client.generated_protobuf import bittrade_pb2
from cards_client.client.client import CardsClient
from cards_client.generated_protobuf import base_pb2 as cards_pb2
from core_wallet_client.client.client import CoreWalletClient
from core_wallet_client.generated_protobuf import core_wallet_pb2
from vault_client.client.conio_user import ConioUser

from conio_sdk.generated_protobuf import v1_pb2
from conio_sdk.services.conio.sdk import BidBO, AskBO
from etc.settings import DEFAULT_CRYPTO_CURRENCY


class ActivityDetailsVOService:
    _CURRENCIES_BY_CARDS_ID = {
        i[1]: i[0] for i in cards_pb2.Currency.items()
    }

    _CARDS_STATUS_TO_WALLET_STATUS = {
        cards_pb2.INIT: v1_pb2.PROCESSING,
        cards_pb2.AUTHORIZING: v1_pb2.PROCESSING,
        cards_pb2.UNAUTHORIZED: v1_pb2.ERROR,
        cards_pb2.AUTHORIZED: v1_pb2.COMPLETED,
        cards_pb2.CANCELLING: v1_pb2.CANCELLED,
        cards_pb2.CANCELLED: v1_pb2.CANCELLED,
        cards_pb2.SETTLING: v1_pb2.PROCESSING,
        cards_pb2.SETTLED: v1_pb2.COMPLETED,
        cards_pb2.REFUNDING: v1_pb2.REFUND,
        cards_pb2.REFUNDED: v1_pb2.REFUND,
        cards_pb2.TIMEOUT: v1_pb2.ERROR,
        cards_pb2.ERROR: v1_pb2.ERROR
    }
    _CURRENCIES_BY_PROTO = {
        i[1]: i[0] for i in bittrade_pb2.Currency.items()
    }
    _MIN_CONFIRMATIONS = 3

    def __init__(
            self,
            bithustler_client: BithustlerClient,
            cards_client: CardsClient,
            core_wallet_client: CoreWalletClient,
            quoting_client: BitquoteClient,
            trading_client: BittradeClient):
        self._bithustler_client = bithustler_client
        self._cards_client = cards_client
        self._core_wallet_client = core_wallet_client
        self._quoting_client = quoting_client
        self._trading_client = trading_client

    def get_activity_details(self, msg: v1_pb2.MsgActivityDetails, user: ConioUser) -> v1_pb2.Activity:
        activity_id, fiat_currency = msg.activity_id, msg.fiat_currency
        activity = v1_pb2.Activity()
        activity.activity_id = activity_id
        payer_id = user.cards_integration_id
        wallet_id = user.get_wallet(DEFAULT_CRYPTO_CURRENCY)['reference_id']
        if activity_id.startswith('__SELL__'):
            self._add_ask_data(wallet_id, activity_id[len('__SELL__'):], activity)
        elif activity_id.startswith('__BID__'):
            self._add_bid_data(payer_id, wallet_id, activity_id[len('__BID__'):], activity)
        else:
            self._add_btc_tx_data(wallet_id, activity_id[len('__BTC__'):], activity, fiat_currency)
        return activity

    def _add_bid_data(self, payer_id: str, wallet_id: str, bid_id: str, activity: v1_pb2.Activity) -> None:
        bid = self._get_bid(bid_id)
        activity.bid.MergeFrom(v1_pb2.Bid(
            id=bid.id,
            status=bid.status,
            satoshi=bid.satoshi,
            fiat_amount=bid.fiat_amount - bid.fee,
            currency=bid.currency,
            created_at=bid.created_at,
            updated_at=bid.updated_at,
            payment_id=bid.payment_id,
            fee=bid.fee))
        if bid.btc_transaction_id:
            self._add_btc_tx_data(wallet_id, bid.btc_transaction_id, activity, bid.currency)
            activity.transaction.incoming = True
            activity.transaction.wallet_recipients.append(bid.address_id)
            activity.transaction.fiat = bid.fiat_amount
        if bid.payment_id:
            cards_payment = self._cards_client.get_payments_by_id(
                cards_pb2.MsgGetPayments(
                    payer_id=payer_id,
                    payment_ids=[bid.payment_id]
                )
            ).data.payments[0]
            activity.payment.MergeFrom(v1_pb2.CardIntegrationPayment(
                    id=cards_payment.id, amount=cards_payment.amount,
                    currency=self._CURRENCIES_BY_CARDS_ID[cards_payment.currency],
                    status=self._CARDS_STATUS_TO_WALLET_STATUS[cards_payment.status],
                    created_at=cards_payment.created_at, updated_at=cards_payment.updated_at
                ))
        activity.activity_type = v1_pb2.ACTIVITY_BUY

    def _add_ask_data(self, wallet_id: str, ask_id: str, activity: v1_pb2.Activity) -> None:
        ask = self._get_ask(ask_id)
        activity.ask.MergeFrom(v1_pb2.Ask(
            id=ask.id,
            status=ask.status,
            satoshi=ask.satoshi,
            fiat_amount=ask.fiat_amount + ask.fee,
            currency=ask.currency,
            created_at=ask.created_at,
            updated_at=ask.updated_at,
            fee=ask.fee
        ))
        if ask.btc_transaction_id:
            self._add_btc_tx_data(wallet_id, ask.btc_transaction_id, activity, ask.currency)
            activity.transaction.incoming = False
            activity.transaction.fiat = ask.fiat_amount
        if ask.sell_id:
            sell = self._bithustler_client.get_sell(ask.sell_id)
            activity.sell.id = sell.id
            activity.sell.sell_method_id = sell.sell_method_id
            activity.sell.status = sell.status
            if sell.wiretransfer.cro:
                activity.sell.cro = sell.wiretransfer.cro
            if sell.iban:
                activity.sell.iban = sell.iban
        elif ask.sell_method_id is not None:
            activity.sell.MergeFrom(v1_pb2.Sell(
                id='',
                sell_method_id=ask.sell_method_id or '',
                status=v1_pb2.INIT,
                cro='',
                iban=''
            ))
        activity.activity_type = v1_pb2.ACTIVITY_SELL

    def _add_btc_tx_data(
            self, wallet_id: str, btc_tx_id: str,
            activity: v1_pb2.Activity, fiat_currency: str) -> v1_pb2.Activity:
        btc_tx = self._core_wallet_client.get_transactions(wallet_id, [btc_tx_id])[0]
        fiat = float(self._quoting_client.get_historical_price(btc_tx.amount, btc_tx.timestamp, fiat_currency)) * \
            (btc_tx.amount * 10 ** -8)
        activity.activity_type = btc_tx.incoming and v1_pb2.ACTIVITY_RECV or v1_pb2.ACTIVITY_SEND

        activity.transaction.MergeFrom(v1_pb2.Transaction(
            hash=btc_tx.hash, amount=btc_tx.amount, timestamp=btc_tx.timestamp, recipients=btc_tx.recipients,
            # senders=btc_tx.senders,
            wallet_recipients=btc_tx.wallet_recipients,
            fiat=fiat,
            status=btc_tx.status, incoming=btc_tx.incoming, height=btc_tx.height, spent=btc_tx.spent,
            confirmations=btc_tx.confirmations, fees=btc_tx.fees, fee_per_byte=btc_tx.fee_per_byte,
            included_at=btc_tx.included_at, confirmed_at=btc_tx.confirmed_at,
            type=self._core_wallet_transaction_type_to_bff_transaction_type(btc_tx.type),
            parent=btc_tx.parent, is_local=btc_tx.is_local
        ))

    @classmethod
    def _core_wallet_transaction_type_to_bff_transaction_type(cls, txtype: int) -> int:
        txtypes = {
            core_wallet_pb2.TRANSACTION_TYPE_GENERIC: v1_pb2.TRANSACTION_TYPE_GENERIC,
            core_wallet_pb2.TRANSACTION_TYPE_CPFP_CHILD: v1_pb2.TRANSACTION_TYPE_CPFP_CHILD,
            core_wallet_pb2.TRANSACTION_TYPE_CPFP_PARENT: v1_pb2.TRANSACTION_TYPE_CPFP_PARENT,
            core_wallet_pb2.TRANSACTION_TYPE_RBF_CURRENT: v1_pb2.TRANSACTION_TYPE_RBF,
            core_wallet_pb2.TRANSACTION_TYPE_REDEPOSIT: v1_pb2.TRANSACTION_TYPE_REDEPOSIT
        }
        return txtypes[txtype]

    def _get_bid(self, bid_id: str) -> BidBO:
        bid = self._trading_client.get_bid(bid_id)
        return BidBO(
            bid.id, bid.status, bid.satoshi,
            bid.fiat_amount.fiat_value / 10 ** bid.fiat_amount.decimal_digits,
            self._CURRENCIES_BY_PROTO[bid.currency],
            bid.created_at, bid.updated_at,
            bid.payment_id, bid.btc_transaction_id,
            bid.explicit_fees.fiat_value / 10 ** bid.explicit_fees.decimal_digits,
            bid.address_id)

    def _get_ask(self, ask_id: str) -> AskBO:
        ask = self._trading_client.get_ask(ask_id)
        return AskBO(
            ask.id, ask.status, ask.satoshi,
            ask.fiat_amount.fiat_value / 10 ** ask.fiat_amount.decimal_digits,
            self._CURRENCIES_BY_PROTO[ask.currency],
            ask.created_at, ask.updated_at,
            ask.sell_id, ask.sell_method_id, ask.btc_transaction_id,
            fee=ask.explicit_fees.fiat_value / 10 ** ask.explicit_fees.decimal_digits)
