# 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 __future__ import absolute_import, division, print_function

import binascii

import pytest

from test_bindings import _box_from_seed_vectors

from nacl.encoding import HexEncoder
from nacl.exceptions import CryptoError
from nacl.public import Box, PrivateKey, PublicKey
from nacl.utils import random


VECTORS = [
    # privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext
    (
        b"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
        b"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
        b"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb",
        b"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
        b"69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37",
        (b"be075fc53c81f2d5cf141316ebeb0c7b5228c52a4c62cbd44b66849b64244ffce5e"
         b"cbaaf33bd751a1ac728d45e6c61296cdc3c01233561f41db66cce314adb310e3be8"
         b"250c46f06dceea3a7fa1348057e2f6556ad6b1318a024a838f21af1fde048977eb4"
         b"8f59ffd4924ca1c60902e52f0a089bc76897040e082f937763848645e0705"),
        (b"f3ffc7703f9400e52a7dfb4b3d3305d98e993b9f48681273c29650ba32fc76ce483"
         b"32ea7164d96a4476fb8c531a1186ac0dfc17c98dce87b4da7f011ec48c97271d2c2"
         b"0f9b928fe2270d6fb863d51738b48eeee314a7cc8ab932164548e526ae902243685"
         b"17acfeabd6bb3732bc0e9da99832b61ca01b6de56244a9e88d5f9b37973f622a43d"
         b"14a6599b1f654cb45a74e355a5"),
    ),
]


def test_generate_private_key():
    PrivateKey.generate()


def test_generate_private_key_from_random_seed():
    PrivateKey.from_seed(random(PrivateKey.SEED_SIZE))


@pytest.mark.parametrize(
    (
        "seed", "public_key", "secret_key"
    ),
    _box_from_seed_vectors()
)
def test_generate_private_key_from_seed(seed, public_key, secret_key):
    prvt = PrivateKey.from_seed(seed, encoder=HexEncoder)
    sk = binascii.unhexlify(secret_key)
    pk = binascii.unhexlify(public_key)
    assert bytes(prvt) == sk
    assert bytes(prvt.public_key) == pk


def test_box_creation():
    pub = PublicKey(
        b"ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798",
        encoder=HexEncoder,
    )
    priv = PrivateKey(
        b"5c2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798",
        encoder=HexEncoder,
    )
    Box(priv, pub)


def test_box_decode():
    pub = PublicKey(
        b"ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798",
        encoder=HexEncoder,
    )
    priv = PrivateKey(
        b"5c2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798",
        encoder=HexEncoder,
    )
    b1 = Box(priv, pub)
    b2 = Box.decode(b1._shared_key)
    assert b1._shared_key == b2._shared_key


def test_box_bytes():
    pub = PublicKey(
        b"ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798",
        encoder=HexEncoder,
    )
    priv = PrivateKey(
        b"5c2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798",
        encoder=HexEncoder,
    )
    b = Box(priv, pub)
    assert bytes(b) == b._shared_key


@pytest.mark.parametrize(
    (
        "privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
        "ciphertext",
    ),
    VECTORS,
)
def test_box_encryption(
        privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext):
    pubalice = PublicKey(pubalice, encoder=HexEncoder)
    privbob = PrivateKey(privbob, encoder=HexEncoder)

    box = Box(privbob, pubalice)
    encrypted = box.encrypt(
        binascii.unhexlify(plaintext),
        binascii.unhexlify(nonce),
        encoder=HexEncoder,
    )

    expected = binascii.hexlify(
        binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext),
    )

    assert encrypted == expected
    assert encrypted.nonce == nonce
    assert encrypted.ciphertext == ciphertext


@pytest.mark.parametrize(
    (
        "privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
        "ciphertext",
    ),
    VECTORS,
)
def test_box_decryption(
        privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext):
    pubbob = PublicKey(pubbob, encoder=HexEncoder)
    privalice = PrivateKey(privalice, encoder=HexEncoder)

    box = Box(privalice, pubbob)

    nonce = binascii.unhexlify(nonce)
    decrypted = binascii.hexlify(
        box.decrypt(ciphertext, nonce, encoder=HexEncoder),
    )

    assert decrypted == plaintext


@pytest.mark.parametrize(
    (
        "privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
        "ciphertext",
    ),
    VECTORS,
)
def test_box_decryption_combined(
        privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext):
    pubbob = PublicKey(pubbob, encoder=HexEncoder)
    privalice = PrivateKey(privalice, encoder=HexEncoder)

    box = Box(privalice, pubbob)

    combined = binascii.hexlify(
        binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext),
    )
    decrypted = binascii.hexlify(box.decrypt(combined, encoder=HexEncoder))

    assert decrypted == plaintext


@pytest.mark.parametrize(
    (
        "privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
        "ciphertext",
    ),
    VECTORS,
)
def test_box_optional_nonce(
        privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext):
    pubbob = PublicKey(pubbob, encoder=HexEncoder)
    privalice = PrivateKey(privalice, encoder=HexEncoder)

    box = Box(privalice, pubbob)

    encrypted = box.encrypt(binascii.unhexlify(plaintext), encoder=HexEncoder)

    decrypted = binascii.hexlify(box.decrypt(encrypted, encoder=HexEncoder))

    assert decrypted == plaintext


@pytest.mark.parametrize(
    (
        "privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
        "ciphertext",
    ),
    VECTORS,
)
def test_box_encryption_generates_different_nonces(
        privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext):
    pubbob = PublicKey(pubbob, encoder=HexEncoder)
    privalice = PrivateKey(privalice, encoder=HexEncoder)

    box = Box(privalice, pubbob)

    nonce_0 = box.encrypt(binascii.unhexlify(plaintext),
                          encoder=HexEncoder).nonce

    nonce_1 = box.encrypt(binascii.unhexlify(plaintext),
                          encoder=HexEncoder).nonce

    assert nonce_0 != nonce_1


@pytest.mark.parametrize(
    (
        "privalice", "pubalice", "privbob", "pubbob", "nonce", "plaintext",
        "ciphertext",
    ),
    VECTORS,
)
def test_box_failed_decryption(
        privalice, pubalice, privbob, pubbob, nonce, plaintext, ciphertext):
    pubbob = PublicKey(pubbob, encoder=HexEncoder)
    privbob = PrivateKey(privbob, encoder=HexEncoder)

    # this cannot decrypt the ciphertext! the ciphertext must be decrypted by
    # (privalice, pubbob) or (privbob, pubalice)
    box = Box(privbob, pubbob)

    with pytest.raises(CryptoError):
        box.decrypt(ciphertext, binascii.unhexlify(nonce), encoder=HexEncoder)


def test_box_wrong_length():
    with pytest.raises(ValueError):
        PublicKey(b"")
    with pytest.raises(TypeError):
        PrivateKey(b"")

    pub = PublicKey(
        b"ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798",
        encoder=HexEncoder,
    )
    priv = PrivateKey(
        b"5c2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798",
        encoder=HexEncoder,
    )
    b = Box(priv, pub)
    with pytest.raises(ValueError):
        b.encrypt(b"", b"")
    with pytest.raises(ValueError):
        b.decrypt(b"", b"")


def check_type_error(expected, f, *args):
    with pytest.raises(TypeError) as e:
        f(*args)
    assert expected in str(e.value)


def test_wrong_types():
    priv = PrivateKey.generate()

    check_type_error(("PrivateKey must be created from a 32 bytes long "
                      "raw secret key"),
                     PrivateKey, 12)
    check_type_error(("PrivateKey must be created from a 32 bytes long "
                      "raw secret key"),
                     PrivateKey, priv)
    check_type_error(("PrivateKey must be created from a 32 bytes long "
                      "raw secret key"),
                     PrivateKey, priv.public_key)

    check_type_error("PublicKey must be created from 32 bytes",
                     PublicKey, 13)
    check_type_error("PublicKey must be created from 32 bytes",
                     PublicKey, priv)
    check_type_error("PublicKey must be created from 32 bytes",
                     PublicKey, priv.public_key)

    check_type_error("Box must be created from a PrivateKey and a PublicKey",
                     Box, priv, "not a public key")
    check_type_error("Box must be created from a PrivateKey and a PublicKey",
                     Box, priv.encode(), priv.public_key.encode())
    check_type_error("Box must be created from a PrivateKey and a PublicKey",
                     Box, priv, priv.public_key.encode())
    check_type_error("Box must be created from a PrivateKey and a PublicKey",
                     Box, priv.encode(), priv.public_key)

    check_type_error("seed must be a 32 bytes long",
                     PrivateKey.from_seed, b"1")
