import json

import pytest

import moto.server as server
from moto.dynamodb_v20111205 import dynamodb_backends

"""
Test the different server responses
Docs:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Appendix.APIv20111205.html
"""

TABLE_NAME = "my_table_name"
TABLE_WITH_RANGE_NAME = "my_table_with_range_name"


@pytest.fixture(autouse=True, name="test_client")
def fixture_test_client():
    backend = server.create_backend_app("dynamodb_v20111205")
    test_client = backend.test_client()

    yield test_client

    for _, backend in dynamodb_backends.items():
        backend.reset()


def test_404(test_client):
    res = test_client.get("/")
    assert res.status_code == 404


def test_table_list(test_client):
    headers = {"X-Amz-Target": "TestTable.ListTables"}
    res = test_client.get("/", headers=headers)
    assert json.loads(res.data) == {"TableNames": []}


def test_create_table(test_client):
    res = create_table(test_client)
    res = json.loads(res.data)["Table"]
    assert "CreationDateTime" in res
    del res["CreationDateTime"]
    assert res == {
        "KeySchema": {
            "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"},
            "RangeKeyElement": {"AttributeName": "rkey", "AttributeType": "N"},
        },
        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
        "TableName": TABLE_WITH_RANGE_NAME,
        "TableStatus": "ACTIVE",
        "ItemCount": 0,
        "TableSizeBytes": 0,
    }

    headers = {"X-Amz-Target": "TestTable.ListTables"}
    res = test_client.get("/", headers=headers)
    res = json.loads(res.data)
    assert res == {"TableNames": [TABLE_WITH_RANGE_NAME]}


def test_create_table_without_range_key(test_client):
    res = create_table(test_client, use_range_key=False)
    res = json.loads(res.data)["Table"]
    assert "CreationDateTime" in res
    del res["CreationDateTime"]
    assert res == {
        "KeySchema": {
            "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"}
        },
        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
        "TableName": TABLE_NAME,
        "TableStatus": "ACTIVE",
        "ItemCount": 0,
        "TableSizeBytes": 0,
    }

    headers = {"X-Amz-Target": "TestTable.ListTables"}
    res = test_client.get("/", headers=headers)
    res = json.loads(res.data)
    assert res == {"TableNames": [TABLE_NAME]}


# This test is pointless, as we treat DynamoDB as a global resource
def test_create_table_in_different_regions(test_client):
    create_table(test_client)
    create_table(test_client, name="Table2", region="us-west-2")

    headers = {"X-Amz-Target": "TestTable.ListTables"}
    res = test_client.get("/", headers=headers)
    res = json.loads(res.data)
    assert res == {"TableNames": [TABLE_WITH_RANGE_NAME, "Table2"]}


def test_update_item(test_client):
    create_table(test_client)

    headers, res = put_item(test_client)

    # UpdateItem
    headers["X-Amz-Target"] = "DynamoDB_20111205.UpdateItem"
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
        "AttributeUpdates": {"new_att": {"Value": {"SS": ["val"]}, "Action": "PUT"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)

    # UpdateItem
    headers["X-Amz-Target"] = "DynamoDB_20111205.UpdateItem"
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
        "AttributeUpdates": {"new_n": {"Value": {"N": "42"}, "Action": "PUT"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["ConsumedCapacityUnits"] == 0.5
    assert res["Attributes"] == {
        "hkey": "customer",
        "name": "myname",
        "rkey": "12341234",
        "new_att": ["val"],
        "new_n": "42",
    }

    # UpdateItem - multiples
    headers["X-Amz-Target"] = "DynamoDB_20111205.UpdateItem"
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
        "AttributeUpdates": {
            "new_n": {"Value": {"N": 7}, "Action": "ADD"},
            "new_att": {"Value": {"S": "val2"}, "Action": "ADD"},
            "name": {"Action": "DELETE"},
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["ConsumedCapacityUnits"] == 0.5
    assert res["Attributes"] == {
        "hkey": "customer",
        "rkey": "12341234",
        "new_att": ["val", "val2"],
        "new_n": "49",
    }

    # GetItem
    headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res["Item"]["new_att"] == {"SS": ["val", "val2"]}
    assert res["Item"]["new_n"] == {"N": "49"}
    assert "name" not in res["Item"]


@pytest.mark.parametrize(
    "use_range_key", [True, False], ids=["using range key", "using hash key only"]
)
def test_delete_table(use_range_key, test_client):
    create_table(test_client, use_range_key=use_range_key)

    headers = {"X-Amz-Target": "DynamoDB_20111205.DeleteTable"}
    name = TABLE_WITH_RANGE_NAME if use_range_key else TABLE_NAME
    test_client.post("/", headers=headers, json={"TableName": name})

    headers = {"X-Amz-Target": "DynamoDB_20111205.ListTables"}
    res = test_client.post("/", headers=headers)
    res = json.loads(res.data)
    assert res == {"TableNames": []}


def test_delete_unknown_table(test_client):
    headers = {"X-Amz-Target": "DynamoDB_20111205.DeleteTable"}
    res = test_client.post("/", headers=headers, json={"TableName": "unknown_table"})
    assert res.status_code == 400

    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_describe_table(test_client):
    create_table(test_client)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.DescribeTable",
        "Content-Type": "application/x-amz-json-1.0",
    }
    res = test_client.post(
        "/", headers=headers, json={"TableName": TABLE_WITH_RANGE_NAME}
    )
    res = json.loads(res.data)["Table"]
    assert "CreationDateTime" in res
    del res["CreationDateTime"]
    assert res == {
        "KeySchema": {
            "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"},
            "RangeKeyElement": {"AttributeName": "rkey", "AttributeType": "N"},
        },
        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
        "TableName": TABLE_WITH_RANGE_NAME,
        "TableStatus": "ACTIVE",
        "ItemCount": 0,
        "TableSizeBytes": 0,
    }


def test_describe_missing_table(test_client):
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.DescribeTable",
        "Content-Type": "application/x-amz-json-1.0",
    }
    res = test_client.post("/", headers=headers, json={"TableName": "unknown_table"})
    assert res.status_code == 400
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


@pytest.mark.parametrize(
    "use_range_key", [True, False], ids=["using range key", "using hash key only"]
)
def test_update_table(test_client, use_range_key):
    table_name = TABLE_WITH_RANGE_NAME if use_range_key else TABLE_NAME
    create_table(test_client, use_range_key=use_range_key)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.UpdateTable",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_data = {
        "TableName": table_name,
        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 15},
    }
    test_client.post("/", headers=headers, json=request_data)

    # DescribeTable - verify the throughput is persisted
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.DescribeTable",
        "Content-Type": "application/x-amz-json-1.0",
    }
    res = test_client.post("/", headers=headers, json={"TableName": table_name})
    throughput = json.loads(res.data)["Table"]["ProvisionedThroughput"]

    assert throughput == {"ReadCapacityUnits": 5, "WriteCapacityUnits": 15}


def test_put_return_none(test_client):
    create_table(test_client)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.PutItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Item": {
            "hkey": {"S": "customer"},
            "rkey": {"N": "12341234"},
            "name": {"S": "myname"},
        },
        "ReturnValues": "NONE",
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    # This seems wrong - it should return nothing, considering return_values is set to none
    assert res["Attributes"] == {
        "hkey": "customer",
        "name": "myname",
        "rkey": "12341234",
    }


def test_put_return_none_without_range_key(test_client):
    create_table(test_client, use_range_key=False)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.PutItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_NAME,
        "Item": {"hkey": {"S": "customer"}, "name": {"S": "myname"}},
        "ReturnValues": "NONE",
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    # This seems wrong - it should return nothing, considering return_values is set to none
    assert res["Attributes"] == {"hkey": "customer", "name": "myname"}


def test_put_item_from_unknown_table(test_client):
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.PutItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": "unknown_table",
        "Item": {
            "hkey": {"S": "customer"},
            "rkey": {"N": "12341234"},
            "name": {"S": "myname"},
        },
        "ReturnValues": "NONE",
    }
    res = test_client.post("/", headers=headers, json=request_body)

    assert res.status_code == 400
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_get_item_from_unknown_table(test_client):
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.GetItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": "unknown_table",
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)

    assert res.status_code == 404
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


@pytest.mark.parametrize(
    "use_range_key", [True, False], ids=["using range key", "using hash key only"]
)
def test_get_unknown_item_from_table(use_range_key, test_client):
    create_table(test_client, use_range_key=use_range_key)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.GetItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    table_name = TABLE_WITH_RANGE_NAME if use_range_key else TABLE_NAME
    request_body = {
        "TableName": table_name,
        "Key": {"HashKeyElement": {"S": "customer"}},
    }
    if use_range_key:
        request_body["Key"]["RangeKeyElement"] = {"N": "12341234"}
    res = test_client.post("/", headers=headers, json=request_body)

    assert res.status_code == 404
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_get_item_without_range_key(test_client):
    create_table(test_client)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.GetItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {"HashKeyElement": {"S": "customer"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)

    assert res.status_code == 400
    assert json.loads(res.data) == {
        "__type": "com.amazon.coral.validate#ValidationException"
    }


def test_put_and_get_item(test_client):
    create_table(test_client)

    headers, res = put_item(test_client)

    assert res["ConsumedCapacityUnits"] == 1
    assert res["Attributes"] == {
        "hkey": "customer",
        "name": "myname",
        "rkey": "12341234",
    }

    # GetItem
    headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res["ConsumedCapacityUnits"] == 0.5
    assert res["Item"] == {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341234"},
    }

    # GetItem - return single attribute
    headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
        "AttributesToGet": ["name"],
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res["ConsumedCapacityUnits"] == 0.5
    assert res["Item"] == {"name": {"S": "myname"}}


def test_put_and_get_item_without_range_key(test_client):
    create_table(test_client, use_range_key=False)

    headers, res = put_item(test_client, use_range_key=False)

    assert res["ConsumedCapacityUnits"] == 1
    assert res["Attributes"] == {"hkey": "customer", "name": "myname"}

    # GetItem
    headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
    request_body = {
        "TableName": TABLE_NAME,
        "Key": {"HashKeyElement": {"S": "customer"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res["ConsumedCapacityUnits"] == 0.5
    assert res["Item"] == {"hkey": {"S": "customer"}, "name": {"S": "myname"}}

    # GetItem - return single attribute
    headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
    request_body = {
        "TableName": TABLE_NAME,
        "Key": {"HashKeyElement": {"S": "customer"}},
        "AttributesToGet": ["name"],
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res["ConsumedCapacityUnits"] == 0.5
    assert res["Item"] == {"name": {"S": "myname"}}


def test_scan_simple(test_client):
    create_table(test_client)

    put_item(test_client)
    put_item(test_client, rkey="12341235")
    put_item(test_client, rkey="12341236")

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {"TableName": TABLE_WITH_RANGE_NAME}
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 3
    assert res["ScannedCount"] == 3
    assert res["ConsumedCapacityUnits"] == 1
    assert len(res["Items"]) == 3

    items = res["Items"]
    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341234"},
    } in items
    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341235"},
    } in items
    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341236"},
    } in items


def test_scan_with_filter(test_client):
    create_table(test_client)

    put_item(test_client, rkey="1230", name="somename")
    put_item(test_client, rkey="1234", name=None)
    put_item(test_client, rkey="1246")

    # SCAN specific item
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "ScanFilter": {
            "rkey": {"AttributeValueList": [{"S": "1234"}], "ComparisonOperator": "EQ"}
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 1
    assert res["ScannedCount"] == 3
    assert res["ConsumedCapacityUnits"] == 1
    assert len(res["Items"]) == 1

    items = res["Items"]
    assert {"hkey": {"S": "customer"}, "rkey": {"N": "1234"}} in items

    # SCAN begins_with
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "ScanFilter": {
            "rkey": {
                "AttributeValueList": [{"S": "124"}],
                "ComparisonOperator": "BEGINS_WITH",
            }
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "1246"},
    } in items

    # SCAN contains
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "ScanFilter": {
            "name": {
                "AttributeValueList": [{"S": "mena"}],
                "ComparisonOperator": "CONTAINS",
            }
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "somename"},
        "rkey": {"N": "1230"},
    } in items

    # SCAN null
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "ScanFilter": {"name": {"ComparisonOperator": "NULL"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert {"hkey": {"S": "customer"}, "rkey": {"N": "1234"}} in items

    # SCAN NOT NULL
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "ScanFilter": {"name": {"ComparisonOperator": "NOT_NULL"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert items == [
        {
            "hkey": {"S": "customer"},
            "name": {"S": "somename"},
            "rkey": {"N": "1230"},
        },
        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1246"}},
    ]

    # SCAN between
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "ScanFilter": {
            "rkey": {
                "AttributeValueList": [{"S": "1230"}, {"S": "1240"}],
                "ComparisonOperator": "BETWEEN",
            }
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "somename"},
        "rkey": {"N": "1230"},
    } in items


def test_scan_with_filter_in_table_without_range_key(test_client):
    create_table(test_client, use_range_key=False)

    put_item(test_client, use_range_key=False, hkey="customer1", name=None)
    put_item(test_client, use_range_key=False, hkey="customer2")
    put_item(test_client, use_range_key=False, hkey="customer3", name="special")

    # SCAN specific item
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_NAME,
        "ScanFilter": {
            "name": {
                "AttributeValueList": [{"S": "special"}],
                "ComparisonOperator": "EQ",
            }
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 1
    assert res["ScannedCount"] == 3
    assert res["ConsumedCapacityUnits"] == 1
    assert len(res["Items"]) == 1

    items = res["Items"]
    assert {"hkey": {"S": "customer3"}, "name": {"S": "special"}} in items

    # SCAN begins_with
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_NAME,
        "ScanFilter": {
            "hkey": {
                "AttributeValueList": [{"S": "cust"}],
                "ComparisonOperator": "BEGINS_WITH",
            }
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert len(items) == 3  # all customers start with cust

    # SCAN contains
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_NAME,
        "ScanFilter": {
            "name": {
                "AttributeValueList": [{"S": "yna"}],
                "ComparisonOperator": "CONTAINS",
            }
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert items == [{"hkey": {"S": "customer2"}, "name": {"S": "myname"}}]

    # SCAN null
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_NAME,
        "ScanFilter": {"name": {"ComparisonOperator": "NULL"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert items == [{"hkey": {"S": "customer1"}}]

    # SCAN NOT NULL
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_NAME,
        "ScanFilter": {"name": {"ComparisonOperator": "NOT_NULL"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert len(items) == 2
    assert {"hkey": {"S": "customer2"}, "name": {"S": "myname"}} in items
    assert {"hkey": {"S": "customer3"}, "name": {"S": "special"}} in items


def test_scan_with_undeclared_table(test_client):
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {"TableName": "unknown_table"}
    res = test_client.post("/", headers=headers, json=request_body)
    assert res.status_code == 400
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_query_in_table_without_range_key(test_client):
    create_table(test_client, use_range_key=False)

    put_item(test_client, use_range_key=False)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {"TableName": TABLE_NAME, "HashKeyValue": {"S": "customer"}}
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 1
    assert res["ConsumedCapacityUnits"] == 1
    assert len(res["Items"]) == 1

    items = res["Items"]
    assert {"hkey": {"S": "customer"}, "name": {"S": "myname"}} in items

    # QUERY for unknown value
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {"TableName": TABLE_NAME, "HashKeyValue": {"S": "unknown-value"}}
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    # TODO: We should not get any results here
    # assert res["Count"] == 0
    # assert len(res["Items"]) == 0


def test_query_item_by_hash_only(test_client):
    create_table(test_client)

    put_item(test_client)
    put_item(test_client, rkey="12341235")
    put_item(test_client, rkey="12341236")

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "HashKeyValue": {"S": "customer"},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 3
    assert res["ConsumedCapacityUnits"] == 1
    assert len(res["Items"]) == 3

    items = res["Items"]
    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341234"},
    } in items
    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341235"},
    } in items
    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341236"},
    } in items


def test_query_item_by_range_key(test_client):
    create_table(test_client, use_range_key=True)

    put_item(test_client, rkey="1234")
    put_item(test_client, rkey="1235")
    put_item(test_client, rkey="1247")

    # GT some
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "HashKeyValue": {"S": "customer"},
        "RangeKeyCondition": {
            "AttributeValueList": [{"N": "1235"}],
            "ComparisonOperator": "GT",
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 1
    assert res["ConsumedCapacityUnits"] == 1
    assert len(res["Items"]) == 1

    items = res["Items"]
    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "1247"},
    } in items

    # GT all
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "HashKeyValue": {"S": "customer"},
        "RangeKeyCondition": {
            "AttributeValueList": [{"N": "0"}],
            "ComparisonOperator": "GT",
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 3
    assert len(res["Items"]) == 3

    # GT none
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "HashKeyValue": {"S": "customer"},
        "RangeKeyCondition": {
            "AttributeValueList": [{"N": "9999"}],
            "ComparisonOperator": "GT",
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["ConsumedCapacityUnits"] == 1
    assert res["Items"] == []
    assert res["Count"] == 0

    # CONTAINS some
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "HashKeyValue": {"S": "customer"},
        "RangeKeyCondition": {
            "AttributeValueList": [{"N": "24"}],
            "ComparisonOperator": "CONTAINS",
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert items == [
        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1247"}}
    ]

    # BEGINS_WITH
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "HashKeyValue": {"S": "customer"},
        "RangeKeyCondition": {
            "AttributeValueList": [{"N": "123"}],
            "ComparisonOperator": "BEGINS_WITH",
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert items == [
        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1234"}},
        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1235"}},
    ]

    # CONTAINS
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "HashKeyValue": {"S": "customer"},
        "RangeKeyCondition": {
            "AttributeValueList": [{"N": "0"}, {"N": "1240"}],
            "ComparisonOperator": "BETWEEN",
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    items = json.loads(res.data)["Items"]

    assert items == [
        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1234"}},
        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1235"}},
    ]


def test_query_item_with_undeclared_table(test_client):
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Query",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": "unknown_table",
        "HashKeyValue": {"S": "customer"},
        "RangeKeyCondition": {
            "AttributeValueList": [{"N": "1235"}],
            "ComparisonOperator": "GT",
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    assert res.status_code == 400
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_delete_item(test_client):
    create_table(test_client)

    put_item(test_client)
    put_item(test_client, rkey="12341235")
    put_item(test_client, rkey="12341236")

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.DeleteItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341236"},
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res == {"Attributes": [], "ConsumedCapacityUnits": 0.5}

    # GetItem
    headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Item"]["hkey"] == {"S": "customer"}
    assert res["Item"]["rkey"] == {"N": "12341234"}
    assert res["Item"]["name"] == {"S": "myname"}


def test_update_item_that_doesnt_exist(test_client):
    create_table(test_client)

    # UpdateItem
    headers = {"X-Amz-Target": "DynamoDB_20111205.UpdateItem"}
    request_body = {
        "TableName": "Table1",
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
        "AttributeUpdates": {"new_att": {"Value": {"SS": ["val"]}, "Action": "PUT"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_delete_item_without_range_key(test_client):
    create_table(test_client, use_range_key=False)

    put_item(test_client, use_range_key=False)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.DeleteItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_NAME,
        "Key": {"HashKeyElement": {"S": "customer"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res == {"Attributes": [], "ConsumedCapacityUnits": 0.5}


def test_delete_item_with_return_values(test_client):
    create_table(test_client)

    put_item(test_client)
    put_item(test_client, rkey="12341235")
    put_item(test_client, rkey="12341236")

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.DeleteItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341236"},
        },
        "ReturnValues": "ALL_OLD",
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    assert res == {
        "Attributes": {"hkey": "customer", "name": "myname", "rkey": "12341236"},
        "ConsumedCapacityUnits": 0.5,
    }


def test_delete_unknown_item(test_client):
    create_table(test_client)

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.DeleteItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": TABLE_WITH_RANGE_NAME,
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341236"},
        },
        "ReturnValues": "ALL_OLD",
    }
    res = test_client.post("/", headers=headers, json=request_body)
    assert res.status_code == 400
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_update_item_in_nonexisting_table(test_client):
    # UpdateItem
    headers = {"X-Amz-Target": "DynamoDB_20111205.UpdateItem"}
    request_body = {
        "TableName": "nonexistent",
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341234"},
        },
        "AttributeUpdates": {"new_att": {"Value": {"SS": ["val"]}, "Action": "PUT"}},
    }
    res = test_client.post("/", headers=headers, json=request_body)
    assert res.status_code == 400
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_delete_from_unknown_table(test_client):
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.DeleteItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": "unknown_table",
        "Key": {
            "HashKeyElement": {"S": "customer"},
            "RangeKeyElement": {"N": "12341236"},
        },
        "ReturnValues": "ALL_OLD",
    }
    res = test_client.post("/", headers=headers, json=request_body)
    assert res.status_code == 400
    assert json.loads(res.data) == {
        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
    }


def test_batch_get_item(test_client):
    create_table(test_client)

    put_item(test_client)
    put_item(test_client, rkey="12341235")
    put_item(test_client, rkey="12341236")

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.BatchGetItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "RequestItems": {
            TABLE_WITH_RANGE_NAME: {
                "Keys": [
                    {
                        "HashKeyElement": {"S": "customer"},
                        "RangeKeyElement": {"N": "12341235"},
                    },
                    {
                        "HashKeyElement": {"S": "customer"},
                        "RangeKeyElement": {"N": "12341236"},
                    },
                ],
            }
        }
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)["Responses"]

    assert res["UnprocessedKeys"] == {}
    table_items = [i["Item"] for i in res[TABLE_WITH_RANGE_NAME]["Items"]]
    assert len(table_items) == 2

    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341235"},
    } in table_items
    assert {
        "hkey": {"S": "customer"},
        "name": {"S": "myname"},
        "rkey": {"N": "12341236"},
    } in table_items


def test_batch_get_item_without_range_key(test_client):
    create_table(test_client, use_range_key=False)

    put_item(test_client, use_range_key=False, hkey="customer1")
    put_item(test_client, use_range_key=False, hkey="customer2")
    put_item(test_client, use_range_key=False, hkey="customer3")

    headers = {
        "X-Amz-Target": "DynamoDB_20111205.BatchGetItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "RequestItems": {
            TABLE_NAME: {
                "Keys": [
                    {"HashKeyElement": {"S": "customer1"}},
                    {"HashKeyElement": {"S": "customer3"}},
                ],
            }
        }
    }
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)["Responses"]

    assert res["UnprocessedKeys"] == {}
    table_items = [i["Item"] for i in res[TABLE_NAME]["Items"]]
    assert len(table_items) == 2

    assert {"hkey": {"S": "customer1"}, "name": {"S": "myname"}} in table_items
    assert {"hkey": {"S": "customer3"}, "name": {"S": "myname"}} in table_items


def test_batch_write_item(test_client):
    create_table(test_client)

    # BATCH-WRITE
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.BatchWriteItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "RequestItems": {
            TABLE_WITH_RANGE_NAME: [
                {
                    "PutRequest": {
                        "Item": {"hkey": {"S": "customer"}, "rkey": {"S": "1234"}}
                    }
                },
                {
                    "PutRequest": {
                        "Item": {"hkey": {"S": "customer"}, "rkey": {"S": "1235"}}
                    }
                },
            ],
        }
    }
    test_client.post("/", headers=headers, json=request_body)

    # SCAN - verify all items are present
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {"TableName": TABLE_WITH_RANGE_NAME}
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 2


def test_batch_write_item_without_range_key(test_client):
    create_table(test_client, use_range_key=False)

    # BATCH-WRITE
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.BatchWriteItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "RequestItems": {
            TABLE_NAME: [
                {"PutRequest": {"Item": {"hkey": {"S": "customer"}}}},
                {"PutRequest": {"Item": {"hkey": {"S": "customer2"}}}},
            ],
        }
    }
    test_client.post("/", headers=headers, json=request_body)

    # SCAN - verify all items are present
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.Scan",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {"TableName": TABLE_NAME}
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)

    assert res["Count"] == 2


def put_item(
    test_client, hkey="customer", rkey="12341234", name="myname", use_range_key=True
):
    table_name = TABLE_WITH_RANGE_NAME if use_range_key else TABLE_NAME
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.PutItem",
        "Content-Type": "application/x-amz-json-1.0",
    }
    request_body = {
        "TableName": table_name,
        "Item": {"hkey": {"S": hkey}},
        "ReturnValues": "ALL_OLD",
    }
    if name:
        request_body["Item"]["name"] = {"S": name}
    if rkey and use_range_key:
        request_body["Item"]["rkey"] = {"N": rkey}
    res = test_client.post("/", headers=headers, json=request_body)
    res = json.loads(res.data)
    return headers, res


def create_table(test_client, name=None, region=None, use_range_key=True):
    if not name:
        name = TABLE_WITH_RANGE_NAME if use_range_key else TABLE_NAME
    headers = {
        "X-Amz-Target": "DynamoDB_20111205.CreateTable",
        "Content-Type": "application/x-amz-json-1.0",
    }
    if region:
        headers["Host"] = f"dynamodb.{region}.amazonaws.com"
    request_body = {
        "TableName": name,
        "KeySchema": {
            "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"}
        },
        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
    }
    if use_range_key:
        request_body["KeySchema"]["RangeKeyElement"] = {
            "AttributeName": "rkey",
            "AttributeType": "N",
        }
    return test_client.post("/", headers=headers, json=request_body)
