import json
from unittest import TestCase, mock

import pytest

from ...utils.jwe import JweRecipient
from .. import crypto as test_module
from ..error import WalletError
from ..key_type import BLS12381G1, ED25519
from ..util import str_to_b64

SEED_B64 = "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA="
SEED = "00000000000000000000000000000000"
MESSAGE = b"Hello World"


class TestCrypto(TestCase):
    def test_validate_seed(self):
        assert test_module.validate_seed(None) is None

        assert SEED.encode("ascii") == test_module.validate_seed(SEED_B64)
        assert SEED.encode("ascii") == test_module.validate_seed(SEED)

        with pytest.raises(test_module.WalletError) as excinfo:
            test_module.validate_seed({"bad": "seed"})
        assert "value is not a string or bytes" in str(excinfo.value)

        with pytest.raises(test_module.WalletError) as excinfo:
            test_module.validate_seed(f"{SEED}{SEED}")
        assert "value must be 32 bytes in length" in str(excinfo.value)

    def test_seeds_keys(self):
        assert len(test_module.seed_to_did(SEED)) in (22, 23)

        (public_key, secret_key) = test_module.create_keypair(test_module.ED25519)
        assert public_key
        assert secret_key

        assert test_module.sign_pk_from_sk(secret_key) in secret_key

    def test_decode_pack_message_x(self):
        with (
            mock.patch.object(
                test_module, "decode_pack_message_outer", mock.MagicMock()
            ) as mock_decode_outer,
            mock.patch.object(
                test_module, "extract_payload_key", mock.MagicMock()
            ) as mock_extract,
        ):
            mock_decode_outer.return_value = (b"wrapper", {"my": b"recip"}, True)

            with pytest.raises(ValueError) as excinfo:
                test_module.decode_pack_message(b"encrypted", lambda x: None)
            assert "No corresponding recipient key found" in str(excinfo.value)

            mock_extract.return_value = (b"payload_key", None)
            with pytest.raises(ValueError) as excinfo:
                test_module.decode_pack_message(b"encrypted", lambda x: b"recip_secret")
            assert "Sender public key not provided" in str(excinfo.value)

    def test_did_is_self_certified(self):
        did = "Av63wJYM7xYR4AiygYq4c3"
        verkey = "6QSduYdf8Bi6t8PfNm5vNomGWDtXhmMmTRzaciudBXYJ"
        assert test_module.did_is_self_certified(did, verkey)
        verkey = "~PKAYz8Ev4yoQgr2LaMAWFx"
        assert test_module.did_is_self_certified(did, verkey)
        verkey = "ABUF7uxYTxZ6qYdZ4G9e1Gi"
        assert not test_module.did_is_self_certified(did, verkey)
        did = "6YnVN5Qdb6mqimTIQcQmSXrHXKdTEdRn5YHZReezUTvta"
        verkey = "6QSduYdf8Bi6t8PfNm5vNomGWDtXhmMmTRzaciudBXYJ"
        assert not test_module.did_is_self_certified(did, verkey)

    def test_decode_pack_message_outer_x(self):
        with pytest.raises(ValueError) as excinfo:
            test_module.decode_pack_message_outer(json.dumps({"invalid": "content"}))
        assert "Invalid packed message" in str(excinfo.value)

        recips = str_to_b64(
            json.dumps(
                {
                    "enc": "xchacha20poly1305_ietf",
                    "typ": "JWM/1.0",
                    "alg": "Quadruple rot-13",
                    "recipients": "not a list",
                }
            )
        )
        with pytest.raises(ValueError) as excinfo:
            test_module.decode_pack_message_outer(
                json.dumps(
                    {
                        "protected": recips,
                        "iv": "MTIzNDU",
                        "tag": "MTIzNDU",
                        "ciphertext": "MTIzNDU",
                    }
                )
            )
        assert "Invalid packed message" in str(excinfo.value)

        recips = str_to_b64(
            json.dumps(
                {
                    "enc": "xchacha20poly1305_ietf",
                    "typ": "JWM/1.0",
                    "alg": "Quadruple rot-13",
                    "recipients": [{"encrypted_key": "MTIzNDU"}],
                }
            )
        )
        with pytest.raises(ValueError) as excinfo:
            test_module.decode_pack_message_outer(
                json.dumps(
                    {
                        "protected": recips,
                        "iv": "MTIzNDU",
                        "tag": "MTIzNDU",
                        "ciphertext": "MTIzNDU",
                    }
                )
            )
        assert "Unsupported pack algorithm" in str(excinfo.value)

    def test_extract_pack_recipients_x(self):
        with pytest.raises(ValueError) as excinfo:
            test_module.extract_pack_recipients([JweRecipient(encrypted_key=b"")])
        assert "Blank recipient key" in str(excinfo.value)

        with pytest.raises(ValueError) as excinfo:
            test_module.extract_pack_recipients(
                [JweRecipient(encrypted_key=b"MTIzNDU", header={"kid": "4mZ5TYv4oN"})] * 2
            )
        assert "Duplicate recipient key" in str(excinfo.value)

        with pytest.raises(ValueError) as excinfo:
            test_module.extract_pack_recipients(
                [
                    JweRecipient(
                        encrypted_key=b"MTIzNDU",
                        header={"kid": "4mZ5TYv4oN", "sender": "4mZ5TYv4oN"},
                    )
                ]
            )
        assert "Missing iv" in str(excinfo.value)

        with pytest.raises(ValueError) as excinfo:
            test_module.extract_pack_recipients(
                [
                    JweRecipient(
                        encrypted_key=b"MTIzNDU",
                        header={"kid": "4mZ5TYv4oN", "iv": "MTIzNDU"},
                    )
                ]
            )
        assert "Unexpected iv" in str(excinfo.value)

    def test_sign_ed25519_x_multiple_messages(self):
        with self.assertRaises(WalletError) as context:
            test_module.sign_message([b"message1", b"message2"], b"secret", ED25519)
        assert "ed25519 can only sign a single message" in str(context.exception)

    def test_sign_x_unsupported_key_type(self):
        with self.assertRaises(WalletError) as context:
            test_module.sign_message([b"message1", b"message2"], b"secret", BLS12381G1)
        assert "Unsupported key type: bls12381g1" in str(context.exception)

    def test_verify_ed25519_x_multiple_messages(self):
        with self.assertRaises(WalletError) as context:
            test_module.verify_signed_message(
                [b"message1", b"message2"], b"signature", b"verkey", ED25519
            )
        assert "ed25519 can only verify a single message" in str(context.exception)

    def test_verify_x_unsupported_key_type(self):
        with self.assertRaises(WalletError) as context:
            test_module.verify_signed_message(
                [b"message1", b"message2"], b"signature", b"verkey", BLS12381G1
            )
        assert "Unsupported key type: bls12381g1" in str(context.exception)
