from galaxy.security import idencoding

test_helper_1 = idencoding.IdEncodingHelper(id_secret="secu1")
test_helper_2 = idencoding.IdEncodingHelper(id_secret="secu2")


def test_maximum_length_handling_ascii():
    # Test that id secrets can be up to 56 characters long.
    longest_id_secret = "m" * idencoding.MAXIMUM_ID_SECRET_LENGTH
    helper = idencoding.IdEncodingHelper(id_secret=longest_id_secret)
    helper.encode_id(1)

    # Test that security helper will catch if the id secret is too long.
    threw_exception = False
    longer_id_secret = "m" * (idencoding.MAXIMUM_ID_SECRET_LENGTH + 1)
    try:
        idencoding.IdEncodingHelper(id_secret=longer_id_secret)
    except Exception:
        threw_exception = True

    assert threw_exception

    # Test that different kinds produce different keys even when id secret
    # is very long.
    e11 = helper.encode_id(1, kind="moo")
    e12 = helper.encode_id(1, kind="moo2")

    assert e11 != e12

    # Test that long kinds are rejected because it uses up "too much" randomness
    # from id_secret values. This isn't a strict requirement up but lets just enforce
    # the best practice.
    assertion_error_raised = False
    try:
        helper.encode_id(1, kind="this is a really long kind")
    except AssertionError:
        assertion_error_raised = True

    assert assertion_error_raised


def test_maximum_length_handling_nonascii():
    longest_id_secret = "◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎"
    helper = idencoding.IdEncodingHelper(id_secret=longest_id_secret)
    helper.encode_id(1)

    # Test that security helper will catch if the id secret is too long.
    threw_exception = False
    longer_id_secret = "◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎"
    try:
        idencoding.IdEncodingHelper(id_secret=longer_id_secret)
    except Exception:
        threw_exception = True

    assert threw_exception

    # Test that different kinds produce different keys even when id secret
    # is very long.
    e11 = helper.encode_id(1, kind="moo")
    e12 = helper.encode_id(1, kind="moo2")

    assert e11 != e12


def test_unicode_null_decoding():
    encoded_id = test_helper_1.encode_id(1)
    threw_exception = False
    try:
        test_helper_1.decode_guid(f"{encoded_id[:-1]}\0")
    except Exception:
        threw_exception = True

    assert threw_exception


def test_encode_decode():
    # Different ids are encoded differently
    assert test_helper_1.encode_id(1) != test_helper_1.encode_id(2)
    # But decoding and encoded id brings back to original id
    assert 1 == test_helper_1.decode_id(test_helper_1.encode_id(1))


def test_nested_encoding():
    # Does nothing if not a dict
    assert test_helper_1.encode_all_ids(1) == 1

    # Encodes top-level things ending in _id
    assert test_helper_1.encode_all_ids(dict(history_id=1))["history_id"] == test_helper_1.encode_id(1)
    # ..except tool_id
    assert test_helper_1.encode_all_ids(dict(tool_id=1))["tool_id"] == 1

    # Encodes lists at top level is end in _ids
    expected_ids = [test_helper_1.encode_id(1), test_helper_1.encode_id(2)]
    assert test_helper_1.encode_all_ids(dict(history_ids=[1, 2]))["history_ids"] == expected_ids

    # Encodes nested stuff if and only if recursive set to true.
    nested_dict = dict(objects=dict(history_ids=[1, 2]))
    assert test_helper_1.encode_all_ids(nested_dict)["objects"]["history_ids"] == [1, 2]
    assert test_helper_1.encode_all_ids(nested_dict, recursive=False)["objects"]["history_ids"] == [1, 2]
    assert test_helper_1.encode_all_ids(nested_dict, recursive=True)["objects"]["history_ids"] == expected_ids


def test_per_kind_encode_deocde():
    # Different ids are encoded differently
    assert test_helper_1.encode_id(1, kind="k1") != test_helper_1.encode_id(2, kind="k1")
    # But decoding and encoded id brings back to original id
    assert 1 == test_helper_1.decode_id(test_helper_1.encode_id(1, kind="k1"), kind="k1")


def test_different_secrets_encode_differently():
    assert test_helper_1.encode_id(1) != test_helper_2.encode_id(1)


def test_per_kind_encodes_id_differently():
    assert test_helper_1.encode_id(1) != test_helper_2.encode_id(1, kind="new_kind")


def test_encode_dict():
    test_dict = dict(
        id=1,
        other=2,
        history_id=3,
    )
    encoded_dict = test_helper_1.encode_dict_ids(test_dict)
    assert encoded_dict["id"] == test_helper_1.encode_id(1)
    assert encoded_dict["other"] == 2
    assert encoded_dict["history_id"] == test_helper_1.encode_id(3)


def test_guid_generation():
    guids = set()
    for _ in range(100):
        guids.add(test_helper_1.get_new_guid())
    assert len(guids) == 100  # Not duplicate guids generated.


def test_encode_decode_guid():
    session_key = test_helper_1.get_new_guid()
    encoded_key = test_helper_1.encode_guid(session_key)
    decoded_key = test_helper_1.decode_guid(encoded_key)
    assert session_key == decoded_key, f"{session_key} != {decoded_key}"
