from typing import TYPE_CHECKING

from ..writers import (
    TX_HASH_SIZE,
    write_bytes_fixed,
    write_bytes_reversed,
    write_uint32,
    write_uint64,
)

if TYPE_CHECKING:
    from typing import Protocol, Sequence
    from ..common import SigHashType
    from trezor.messages import PrevTx, SignTx, TxInput, TxOutput
    from apps.common import coininfo

    class SigHasher(Protocol):
        def add_input(self, txi: TxInput, script_pubkey: bytes) -> None:
            ...

        def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None:
            ...

        def hash143(
            self,
            txi: TxInput,
            public_keys: Sequence[bytes | memoryview],
            threshold: int,
            tx: SignTx | PrevTx,
            coin: coininfo.CoinInfo,
            hash_type: int,
        ) -> bytes:
            ...

        def hash341(
            self,
            i: int,
            tx: SignTx | PrevTx,
            sighash_type: SigHashType,
        ) -> bytes:
            ...

        def hash_zip244(
            self,
            txi: TxInput | None,
            script_pubkey: bytes | None,
        ) -> bytes:
            ...


# BIP-0143 hash
class BitcoinSigHasher:
    def __init__(self) -> None:
        from trezor.crypto.hashlib import sha256
        from trezor.utils import HashWriter

        self.h_prevouts = HashWriter(sha256())
        self.h_amounts = HashWriter(sha256())
        self.h_scriptpubkeys = HashWriter(sha256())
        self.h_sequences = HashWriter(sha256())
        self.h_outputs = HashWriter(sha256())

    def add_input(self, txi: TxInput, script_pubkey: bytes) -> None:
        from ..writers import write_bytes_prefixed

        write_bytes_reversed(self.h_prevouts, txi.prev_hash, TX_HASH_SIZE)
        write_uint32(self.h_prevouts, txi.prev_index)
        write_uint64(self.h_amounts, txi.amount)
        write_bytes_prefixed(self.h_scriptpubkeys, script_pubkey)
        write_uint32(self.h_sequences, txi.sequence)

    def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None:
        from ..writers import write_tx_output

        write_tx_output(self.h_outputs, txo, script_pubkey)

    def hash143(
        self,
        txi: TxInput,
        public_keys: Sequence[bytes | memoryview],
        threshold: int,
        tx: SignTx | PrevTx,
        coin: coininfo.CoinInfo,
        hash_type: int,
    ) -> bytes:
        from trezor.crypto.hashlib import sha256
        from trezor.utils import HashWriter
        from .. import scripts
        from ..writers import get_tx_hash

        h_preimage = HashWriter(sha256())

        # nVersion
        write_uint32(h_preimage, tx.version)

        # hashPrevouts
        prevouts_hash = get_tx_hash(self.h_prevouts, double=coin.sign_hash_double)
        write_bytes_fixed(h_preimage, prevouts_hash, TX_HASH_SIZE)

        # hashSequence
        sequence_hash = get_tx_hash(self.h_sequences, double=coin.sign_hash_double)
        write_bytes_fixed(h_preimage, sequence_hash, TX_HASH_SIZE)

        # outpoint
        write_bytes_reversed(h_preimage, txi.prev_hash, TX_HASH_SIZE)
        write_uint32(h_preimage, txi.prev_index)

        # scriptCode
        scripts.write_bip143_script_code_prefixed(
            h_preimage, txi, public_keys, threshold, coin
        )

        # amount
        write_uint64(h_preimage, txi.amount)

        # nSequence
        write_uint32(h_preimage, txi.sequence)

        # hashOutputs
        outputs_hash = get_tx_hash(self.h_outputs, double=coin.sign_hash_double)
        write_bytes_fixed(h_preimage, outputs_hash, TX_HASH_SIZE)

        # nLockTime
        write_uint32(h_preimage, tx.lock_time)

        # nHashType
        write_uint32(h_preimage, hash_type)

        return get_tx_hash(h_preimage, double=coin.sign_hash_double)

    def hash341(
        self,
        i: int,
        tx: SignTx | PrevTx,
        sighash_type: SigHashType,
    ) -> bytes:
        from ..common import tagged_hashwriter
        from ..writers import write_uint8

        h_sigmsg = tagged_hashwriter(b"TapSighash")

        # sighash epoch 0
        write_uint8(h_sigmsg, 0)

        # nHashType
        write_uint8(h_sigmsg, sighash_type & 0xFF)

        # nVersion
        write_uint32(h_sigmsg, tx.version)

        # nLockTime
        write_uint32(h_sigmsg, tx.lock_time)

        # sha_prevouts
        write_bytes_fixed(h_sigmsg, self.h_prevouts.get_digest(), TX_HASH_SIZE)

        # sha_amounts
        write_bytes_fixed(h_sigmsg, self.h_amounts.get_digest(), TX_HASH_SIZE)

        # sha_scriptpubkeys
        write_bytes_fixed(h_sigmsg, self.h_scriptpubkeys.get_digest(), TX_HASH_SIZE)

        # sha_sequences
        write_bytes_fixed(h_sigmsg, self.h_sequences.get_digest(), TX_HASH_SIZE)

        # sha_outputs
        write_bytes_fixed(h_sigmsg, self.h_outputs.get_digest(), TX_HASH_SIZE)

        # spend_type 0 (no tapscript message extension, no annex)
        write_uint8(h_sigmsg, 0)

        # input_index
        write_uint32(h_sigmsg, i)

        return h_sigmsg.get_digest()

    def hash_zip244(
        self,
        txi: TxInput | None,
        script_pubkey: bytes | None,
    ) -> bytes:
        raise NotImplementedError
