from hashlib import sha3_256
from typing import Union

from nacl.signing import SigningKey, VerifyKey
from nacl.exceptions import BadSignatureError
from kasten import generator, Kasten
from kasten.exceptions import InvalidID
from kasten.types import KastenPacked, KastenChecksum

from onionrblocks.customtypes import RawEd25519PrivateKey, RawEd25519PublicKey

class Signed(generator.KastenBaseGenerator):

    @classmethod
    def generate(
            cls,
            packed_bytes: KastenPacked,
            signingKey: Union[SigningKey, RawEd25519PrivateKey]
            ) -> Kasten:
        """Sign a digest of packed bytes, return a Kasten instance of it."""
        # Use libsodium/pynacl (ed25519)
        hashed = sha3_256(packed_bytes).digest()
        try:
            signed = signingKey.sign(hashed)
        except AttributeError:
            signingKey = SigningKey(signingKey)
            signed = signingKey.sign(hashed)
        # The KastenChecksum will be 64 bytes message then 32 bytes of the hash
        # This can be fed right back into VerifyKey without splitting up
        return Kasten(signed, packed_bytes, None,
                      auto_check_generator=False)


    @staticmethod
    def validate_id(
            hash: KastenChecksum,
            packed_bytes: KastenPacked,
            verify_key: Union[VerifyKey, RawEd25519PublicKey]) -> None:
        # Hash is 64 bytes message then 32 bytes of the hash of the packed_bytes
        if len(hash) != 86:
            raise InvalidID("Block not have proper signature length")
        actual_hash = sha3_256(packed_bytes).digest()
        if not isinstance(verify_key, VerifyKey):
            verify_key = VerifyKey(verify_key)

        # Ensure that the digest is correct
        # Done in addition of the signature bc the sha3 can still ID blocks
        # and to prevent swapping sigs
        if actual_hash != hash[64:]:
            raise InvalidID("Invalid sha3_256 digest")
        # Ensure that the signature is correct
        try:
            verify_key.verify(hash)
        except BadSignatureError as e:
            raise InvalidID(repr(e))
