import abc
import decimal
import enum
import typing

from bittrade_client.generated_protobuf import bittrade_pb2
from vault_client.client.conio_user import ConioUser

from conio_sdk.services.conio.user.user_service import ConioUserID
from conio_sdk.services.conio.wallet.bitcoin_wallet_service import SatoshiAmount, BtcAddressID

AskID = typing.NewType('AskID', str)
BidID = typing.NewType('BidID', str)


@enum.unique
class FiatCurrency(enum.IntEnum):
    EUR = 0
    USD = 1


Ask = typing.NamedTuple(
    'Ask',
    (
            ('ask_id', str),
            ('satoshi', int),
            ('fiat_amount', decimal.Decimal),
            ('currency', FiatCurrency),
            ('expiration_timestamp', int),
            ('explicit_fees', decimal.Decimal),
    )
)

WiretransferPayeeInfo = typing.NamedTuple(
    'WiretransferPayeeInfo',
    (
        ('description', str),
        ('holder', str),
        ('holder_iban', str),
    )
)

Bid = typing.NamedTuple(
    'Bid',
    (
        ('bid_id', str),
        ('fiat_amount', decimal.Decimal),
        ('currency', FiatCurrency),
        ('satoshi', int),
        ('expiration_timestamp', int),
        ('explicit_fees', decimal.Decimal),
        ('wiretransfer_payee_info', WiretransferPayeeInfo)
    )
)


PaymentToken = typing.NewType('PaymentToken', str)
MaskedPAN = typing.NewType('MaskedPAN', str)
GestpayUserID = typing.NewType('GestpayUserID', str)
GestpaySubmitPaymentURL = typing.NewType('GestpaySubmitPaymentURL', str)

DataToFinalizeUntokenizedPayment = typing.NamedTuple(
    'DataToFinalizeUntokenizedPayment',
    (
        ('payment_token', PaymentToken),
        ('gp_id', GestpayUserID),
        ('gp_url', GestpaySubmitPaymentURL)
    )
)
DataToFinalizeTokenizedPayment = typing.NamedTuple(
    'DataToFinalizeTokenizedPayment',
    (
        ('payment_token', PaymentToken),
        ('maskedpan', MaskedPAN),
        ('gp_id', GestpayUserID),
        ('gp_url', GestpaySubmitPaymentURL)
    )
)


def __init__(
        self, bid_id: str, fiat_amount: float, currency: str, satoshi: int,
        expiration_timestamp: int, explicit_fees: float):
    self.bid_id = bid_id
    self.fiat_amount = fiat_amount
    self.currency = currency
    self.satoshi = satoshi
    self.expiration_timestamp = expiration_timestamp
    self.explicit_fees = explicit_fees


AskBtcTransactionSignData = typing.NamedTuple(
    'AskBtcTransactionSignData',
    (
        ('path', typing.Sequence[int]),
        ('data_to_sign', bytes)
    )
)

AskBtcTransaction = typing.NamedTuple(
    'AskBtcTransaction',
    (
        ('tx', bytes),
        ('sign_data', typing.Sequence[AskBtcTransactionSignData]),
        ('fees', int)
    )
)


AskTransactionSignature = typing.NamedTuple(
    'AskTransactionSignature',
    (
        ('path', typing.Sequence[int]),
        ('signature', bytes)
    )
)


class TradingService(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def trigger_charge_btc(self, user_id: ConioUserID) -> None:
        pass

    @abc.abstractmethod
    def get_processing_bids_bits(self, user_id: ConioUserID) -> SatoshiAmount:
        pass

    @abc.abstractmethod
    def get_processing_bids_addresses(self, user_id: ConioUserID) -> typing.Sequence[BtcAddressID]:
        pass

    @abc.abstractmethod
    def refresh_ask_price_by_satoshi(
            self, user: ConioUser,
            ask_id: AskID, satoshi_amount: SatoshiAmount,
            fiat_currency: FiatCurrency) -> Ask:
        pass

    @abc.abstractmethod
    def create_ask_by_satoshi(
            self, user: ConioUser,
            satoshi_amount: SatoshiAmount,
            fiat_currency: FiatCurrency) -> Ask:
        pass

    @abc.abstractmethod
    def refresh_ask_satoshi_by_fiat(
            self, user: ConioUser,
            ask_id: AskID,
            fiat_amount: decimal.Decimal,
            fiat_currency: FiatCurrency) -> Ask:
        pass

    @abc.abstractmethod
    def create_ask_satoshi_by_fiat(
            self, user: ConioUser,
            fiat_amount: decimal.Decimal,
            fiat_currency: FiatCurrency) -> Ask:
        pass

    @abc.abstractmethod
    def pay_for_ask(self, user: ConioUser, ask_id: AskID) -> AskBtcTransaction:
        pass

    @abc.abstractmethod
    def finalize_payment_for_ask(self, ask_id: AskID, tx: bytes, *signatures: AskTransactionSignature) -> None:
        pass

    @abc.abstractmethod
    def get_ask_tx_inclusion_blocks(self) -> int:
        pass

    @abc.abstractmethod
    def get_paid_bids(self, user: ConioUser) -> typing.Sequence[bittrade_pb2.Bid]:
        pass

    @abc.abstractmethod
    def refresh_bid_price_by_satoshi(
            self,
            user: ConioUser,
            bid_id: BidID, satoshi_amount: SatoshiAmount, fiat_currency: FiatCurrency,
            use_iban: bool) -> Bid:
        pass

    @abc.abstractmethod
    def create_bid_by_satoshi(
            self, user: ConioUser,
            satoshi_amount: SatoshiAmount,
            fiat_currency: FiatCurrency,
            use_iban: bool) -> Bid:
        pass

    @abc.abstractmethod
    def refresh_bid_satoshi_by_fiat(
            self,
            user: ConioUser,
            bid_id: BidID,
            fiat_amount: decimal.Decimal,
            fiat_currency: FiatCurrency,
            use_iban: bool) -> Bid:
        pass

    @abc.abstractmethod
    def create_bid_satoshi_by_fiat(
            self,
            user: ConioUser,
            fiat_amount: decimal.Decimal,
            fiat_currency: FiatCurrency,
            use_iban: bool) -> Bid:
        pass

    @abc.abstractmethod
    def pay_for_bid_with_existing_payment_method(self, user: ConioUser, bid_id: BidID, payment_method_id: str) \
            -> DataToFinalizeTokenizedPayment:
        pass

    @abc.abstractmethod
    def pay_for_bid_with_new_payment_method(self, user: ConioUser, bid_id: BidID) \
            -> DataToFinalizeUntokenizedPayment:
        pass

    @abc.abstractmethod
    def get_payment_method_id_by_external_reference(self, user: ConioUser, external_reference_id: str)\
            -> typing.Optional[str]:
        pass

    @abc.abstractmethod
    def finalize_bid_with_new_payment_method(
            self, user: ConioUser, bid_id: BidID,
            maskedpan: str, external_reference_id: str) -> None:
        pass

    @abc.abstractmethod
    def finalize_bid_with_existing_payment_method(self, user: ConioUser, bid_id: BidID) -> None:
        pass

    @abc.abstractmethod
    def trigger_wiretransfer_bid(self, bid_id: str) -> None:
        pass

    @abc.abstractmethod
    def wait_for_bid_finalization(self, bid_id: str) -> int:
        pass

    @abc.abstractmethod
    def get_default_explicit_fees(self) -> typing.Sequence[str]:
        pass
