import pytest
import redis
from redis import asyncio as aioredis
from .utility import *
from json import JSONDecoder, JSONEncoder, dumps

jane = {"name": "Jane", "Age": 33, "Location": "Chawton"}

json_num = {"a": {"a": 1, "b": 2, "c": 3}}


async def get_set_json(connection: aioredis.Redis, key, value, path="$"):
    encoder = JSONEncoder()
    await connection.execute_command("json.set", key, path, encoder.encode(value))
    result = await connection.execute_command("json.get", key, path)
    decoder = JSONDecoder()
    return decoder.decode(result)


async def test_basic_json_get_set(async_client: aioredis.Redis):
    key_name = "test-json-key"
    result = await get_set_json(connection=async_client, key=key_name, value=jane)
    assert result, "failed to set JSON value"
    the_type = await async_client.type(key_name)
    assert the_type == "ReJSON-RL"
    assert len(result) == 1
    assert result[0]["name"] == "Jane"
    assert result[0]["Age"] == 33


async def test_access_json_value_as_string(async_client: aioredis.Redis):
    key_name = "test-json-key"
    result = await get_set_json(async_client, key_name, value=jane)
    assert result is not None, "failed to set JSON value"
    # make sure that we have valid JSON here
    the_type = await async_client.type(key_name)
    assert the_type == "ReJSON-RL"
    # you cannot access this key as string
    with pytest.raises(redis.exceptions.ResponseError) as e:
        result = await async_client.get(key_name)

    assert e.value.args[0] == "WRONGTYPE Operation against a key holding the wrong kind of value"


async def test_reset_key_to_string(async_client: aioredis.Redis):
    key_name = "test-json-key"
    result = await get_set_json(async_client, key=key_name, value=jane)
    assert result is not None, "failed to set JSON value"
    # make sure that we have valid JSON here
    the_type = await async_client.type(key_name)
    assert the_type == "ReJSON-RL"

    # set the key to be string - this is legal
    await async_client.set(key_name, "some random value")
    result = await async_client.get(key_name)
    assert result == "some random value"

    # For JSON set the update the root path, we are allowing
    # to change the type to JSON and override it
    result = await get_set_json(async_client, key=key_name, value=jane)
    the_type = await async_client.type(key_name)
    assert the_type == "ReJSON-RL"


async def test_update_value(async_client: aioredis.Redis):
    key_name = "test-json-key"
    result = await get_set_json(async_client, key=key_name, value=json_num)
    assert result is not None, "failed to set JSON value"
    # make sure that we have valid JSON here
    the_type = await async_client.type(key_name)
    assert the_type == "ReJSON-RL"
    result = await get_set_json(async_client, value="0", key=key_name, path="$.a.*")
    assert len(result) == 3
    # make sure that all the values under 'a' where set to 0
    assert result == ["0", "0", "0"]

    # Ensure that after we're changing this into STRING type, it will no longer work
    await async_client.set(key_name, "some random value")
    assert await async_client.type(key_name) == "string"
    with pytest.raises(redis.exceptions.ResponseError) as e:
        await get_set_json(async_client, value="0", key=key_name, path="$.a.*")

    assert e.value.args[0] == "WRONGTYPE Operation against a key holding the wrong kind of value"
    assert await async_client.type(key_name) == "string"


@pytest.mark.parametrize(
    "description,expected_value,expected_type",
    (
        ("array", "[]", "array"),
        ("string", dumps("dragonfly"), "string"),
        ("number", dumps(3.50), "number"),
        ("object", dumps({"dragon": "fly"}, separators=(",", ":")), "object"),
        ("boolean true", "true", "boolean"),
        ("boolean false", "false", "boolean"),
    ),
)
@pytest.mark.asyncio
async def test_arrappend(async_client: aioredis.Redis, description, expected_value, expected_type):
    key_name = "test-json-key"

    await async_client.execute_command("json.set", key_name, "$", "[]")
    await async_client.execute_command("json.arrappend", key_name, "$", expected_value)

    # make sure the value is as expected
    first_element = await async_client.execute_command("json.get", key_name, "$[0]")
    assert first_element == "[{}]".format(expected_value)

    # make sure the type is as expected
    actual_type = await async_client.execute_command("json.type", key_name, "$[0]")
    assert actual_type[0] == expected_type
