# Copyright 2013 Donald Stufft and individual contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Optional

import nacl.bindings
from nacl import encoding
from nacl import exceptions as exc
from nacl.public import (
    PrivateKey as _Curve25519_PrivateKey,
    PublicKey as _Curve25519_PublicKey,
)
from nacl.utils import StringFixer, random


class SignedMessage(bytes):
    """
    A bytes subclass that holds a message that has been signed by a
    :class:`SigningKey`.
    """

    _signature: bytes
    _message: bytes

    @classmethod
    def _from_parts(
        cls, signature: bytes, message: bytes, combined: bytes
    ) -> "SignedMessage":
        obj = cls(combined)
        obj._signature = signature
        obj._message = message
        return obj

    @property
    def signature(self) -> bytes:
        """
        The signature contained within the :class:`SignedMessage`.
        """
        return self._signature

    @property
    def message(self) -> bytes:
        """
        The message contained within the :class:`SignedMessage`.
        """
        return self._message


class VerifyKey(encoding.Encodable, StringFixer):
    """
    The public key counterpart to an Ed25519 SigningKey for producing digital
    signatures.

    :param key: [:class:`bytes`] Serialized Ed25519 public key
    :param encoder: A class that is able to decode the `key`
    """

    def __init__(
        self, key: bytes, encoder: encoding.Encoder = encoding.RawEncoder
    ):
        # Decode the key
        key = encoder.decode(key)
        if not isinstance(key, bytes):
            raise exc.TypeError("VerifyKey must be created from 32 bytes")

        if len(key) != nacl.bindings.crypto_sign_PUBLICKEYBYTES:
            raise exc.ValueError(
                "The key must be exactly %s bytes long"
                % nacl.bindings.crypto_sign_PUBLICKEYBYTES,
            )

        self._key = key

    def __bytes__(self) -> bytes:
        return self._key

    def __hash__(self) -> int:
        return hash(bytes(self))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    def verify(
        self,
        smessage: bytes,
        signature: Optional[bytes] = None,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> bytes:
        """
        Verifies the signature of a signed message, returning the message
        if it has not been tampered with else raising
        :class:`~nacl.exceptions.BadSignatureError`.

        :param smessage: [:class:`bytes`] Either the original messaged or a
            signature and message concated together.
        :param signature: [:class:`bytes`] If an unsigned message is given for
            smessage then the detached signature must be provided.
        :param encoder: A class that is able to decode the secret message and
            signature.
        :rtype: :class:`bytes`
        """
        if signature is not None:
            # If we were given the message and signature separately, validate
            #   signature size and combine them.
            if not isinstance(signature, bytes):
                raise exc.TypeError(
                    "Verification signature must be created from %d bytes"
                    % nacl.bindings.crypto_sign_BYTES,
                )

            if len(signature) != nacl.bindings.crypto_sign_BYTES:
                raise exc.ValueError(
                    "The signature must be exactly %d bytes long"
                    % nacl.bindings.crypto_sign_BYTES,
                )

            smessage = signature + encoder.decode(smessage)
        else:
            # Decode the signed message
            smessage = encoder.decode(smessage)

        return nacl.bindings.crypto_sign_open(smessage, self._key)

    def to_curve25519_public_key(self) -> _Curve25519_PublicKey:
        """
        Converts a :class:`~nacl.signing.VerifyKey` to a
        :class:`~nacl.public.PublicKey`

        :rtype: :class:`~nacl.public.PublicKey`
        """
        raw_pk = nacl.bindings.crypto_sign_ed25519_pk_to_curve25519(self._key)
        return _Curve25519_PublicKey(raw_pk)


class SigningKey(encoding.Encodable, StringFixer):
    """
    Private key for producing digital signatures using the Ed25519 algorithm.

    Signing keys are produced from a 32-byte (256-bit) random seed value. This
    value can be passed into the :class:`~nacl.signing.SigningKey` as a
    :func:`bytes` whose length is 32.

    .. warning:: This **must** be protected and remain secret. Anyone who knows
        the value of your :class:`~nacl.signing.SigningKey` or it's seed can
        masquerade as you.

    :param seed: [:class:`bytes`] Random 32-byte value (i.e. private key)
    :param encoder: A class that is able to decode the seed

    :ivar: verify_key: [:class:`~nacl.signing.VerifyKey`] The verify
        (i.e. public) key that corresponds with this signing key.
    """

    def __init__(
        self,
        seed: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ):
        # Decode the seed
        seed = encoder.decode(seed)
        if not isinstance(seed, bytes):
            raise exc.TypeError(
                "SigningKey must be created from a 32 byte seed"
            )

        # Verify that our seed is the proper size
        if len(seed) != nacl.bindings.crypto_sign_SEEDBYTES:
            raise exc.ValueError(
                "The seed must be exactly %d bytes long"
                % nacl.bindings.crypto_sign_SEEDBYTES
            )

        public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(seed)

        self._seed = seed
        self._signing_key = secret_key
        self.verify_key = VerifyKey(public_key)

    def __bytes__(self) -> bytes:
        return self._seed

    def __hash__(self) -> int:
        return hash(bytes(self))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))

    def __ne__(self, other: object) -> bool:
        return not (self == other)

    @classmethod
    def generate(cls) -> "SigningKey":
        """
        Generates a random :class:`~nacl.signing.SigningKey` object.

        :rtype: :class:`~nacl.signing.SigningKey`
        """
        return cls(
            random(nacl.bindings.crypto_sign_SEEDBYTES),
            encoder=encoding.RawEncoder,
        )

    def sign(
        self,
        message: bytes,
        encoder: encoding.Encoder = encoding.RawEncoder,
    ) -> SignedMessage:
        """
        Sign a message using this key.

        :param message: [:class:`bytes`] The data to be signed.
        :param encoder: A class that is used to encode the signed message.
        :rtype: :class:`~nacl.signing.SignedMessage`
        """
        raw_signed = nacl.bindings.crypto_sign(message, self._signing_key)

        crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES
        signature = encoder.encode(raw_signed[:crypto_sign_BYTES])
        message = encoder.encode(raw_signed[crypto_sign_BYTES:])
        signed = encoder.encode(raw_signed)

        return SignedMessage._from_parts(signature, message, signed)

    def to_curve25519_private_key(self) -> _Curve25519_PrivateKey:
        """
        Converts a :class:`~nacl.signing.SigningKey` to a
        :class:`~nacl.public.PrivateKey`

        :rtype: :class:`~nacl.public.PrivateKey`
        """
        sk = self._signing_key
        raw_private = nacl.bindings.crypto_sign_ed25519_sk_to_curve25519(sk)
        return _Curve25519_PrivateKey(raw_private)
