import abc
import enum
import typing

from core_wallet_client.client.client import FakeOutput
from vault_client.client.conio_user import ConioUser

BIP32PublicKey = typing.NewType('BIP32PublicKey', str)
EncryptedData = typing.NewType('EncryptedData', bytes)
WalletID = typing.NewType('WalletID', str)
SatoshiAmount = typing.NewType('SatoshiAmount', int)
SatoshiAmountWithFees = typing.NamedTuple(
    'SatoshiAmountWithFees',
    (
        ('satoshi_amount', SatoshiAmount),
        ('mining_fees', SatoshiAmount)
     )
)

WalletEncryptedData = typing.NamedTuple(
    'WalletEncryptedData',
    (
        ('encrypted_mnemonic', EncryptedData),
        ('encrypted_seed', EncryptedData),
        ('encrypted_private_key', EncryptedData),
        ('bip32_public_key', BIP32PublicKey),
        ('wallet_id', typing.Optional[WalletID])
    )
)


WalletBalance = typing.NamedTuple(
    'WalletBalance',
    (
        ('confirmed_satoshi_amount', SatoshiAmount),
        ('unconfirmed_satoshi_amount', SatoshiAmount)
    )
)


BtcAddressID = typing.NewType('BtcAddressID', str)


class BTCAddressType(enum.IntEnum):
    MULTI_SIG = 0
    SINGLE_SIG = 1
    NONE = 2
    IMPORTED_SINGLE_SIG = 3


BtcTransactionSignature = typing.NamedTuple(
    'BtcTransactionSignature',
    (
        ('bip44_path', str),
        ('signature', bytes)
    )
)

BtcTransactionDataToSign = typing.NamedTuple(
    'BtcTransactionDataToSign',
    (
        ('path', typing.List[int]),
        ('data_to_sign', bytes),
        ('btc_address_type', BTCAddressType)
    )
)

BtcTransactionHash = typing.NewType('BtcTransactionHash', str)

BtcRawTransaction = typing.NewType('BtcRawTransaction', bytes)
BtcTransactionToBeSigned = typing.NamedTuple(
    'BtcTransactionToBeSigned',
    (
        ('tx', BtcRawTransaction),
        ('sign_data', typing.List[BtcTransactionDataToSign]),
        ('fees', int),
        ('satoshi_amount', SatoshiAmount),
        ('fee_per_byte', typing.Optional[SatoshiAmount]),
        ('locked_balance', typing.Optional[SatoshiAmount])
    )
)


class BitcoinWalletService(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def create_wallet_if_not_exists(self, user: ConioUser, wallet_encrypted_data: WalletEncryptedData) \
            -> WalletEncryptedData:
        pass   # pragma: no cover

    @abc.abstractmethod
    def get_bip32_public_key(self, bitcoin_wallet_id: WalletID) -> BIP32PublicKey:
        pass   # pragma: no cover

    @abc.abstractmethod
    def create_wallet(self, user: ConioUser, wallet_encrypted_data: WalletEncryptedData)\
            -> WalletEncryptedData:
        pass  # pragma: no cover

    @abc.abstractmethod
    def get_wallet_balance(self, bitcoin_wallet_id: WalletID, invalidata_cache: bool = False) -> WalletBalance:
        pass  # pragma: no cover

    @abc.abstractmethod
    def get_current_btc_address(self, bitcoin_wallet_id: WalletID) -> BtcAddressID:
        pass  # pragma: no cover

    @abc.abstractmethod
    def create_transaction(
            self, bitcoin_wallet_id: WalletID,
            btc_address: BtcAddressID,
            satoshi_amount: SatoshiAmount,
            fee_per_byte: typing.Optional[SatoshiAmount] = None,
            fake_outputs: typing.Sequence[FakeOutput] = ()) -> BtcTransactionToBeSigned:
        pass  # pragma: no cover

    @abc.abstractmethod
    def create_all_transaction(
            self, bitcoin_wallet_id: WalletID,
            btc_address: BtcAddressID,
            fee_per_byte: typing.Optional[SatoshiAmount] = None,
            fake_outputs: typing.Sequence[FakeOutput] = ()) \
            -> BtcTransactionToBeSigned:
        pass  # pragma: no cover

    @abc.abstractmethod
    def submit_transaction(
            self, wallet_id: WalletID,
            tx: BtcRawTransaction,
            signatures: typing.List[BtcTransactionSignature]
    ) -> BtcTransactionHash:
        pass  # pragma: no cover

    @abc.abstractmethod
    def get_fees(
            self, wallet_id: WalletID,
            btc_addresses: typing.Sequence[BtcAddressID],
            fee_per_byte: int) -> int:
        pass  # pragma: no cover

    @abc.abstractmethod
    def wallet_has_addresses(self, wallet_id: str) -> bool:
        pass  # pragma: no cover
