import pytest

from moto.dynamodb.exceptions import DynamodbException
from moto.dynamodb.parsing.key_condition_expression import parse_expression


class TestHashKey:
    schema = [{"AttributeName": "job_id", "KeyType": "HASH"}]

    @pytest.mark.parametrize("expression", ["job_id = :id", "job_id = :id "])
    def test_hash_key_only(self, expression):
        eav = {":id": {"S": "asdasdasd"}}
        hash_key_conditions, range_key_conditions, _ = parse_expression(
            expression_attribute_values=eav,
            key_condition_expression=expression,
            schema=self.schema,
            expression_attribute_names={},
        )
        assert hash_key_conditions[0][1] == eav[":id"]
        assert range_key_conditions == []

    def test_unknown_hash_key(self):
        kce = "wrongName = :id"
        eav = {":id": "pk"}
        with pytest.raises(DynamodbException) as exc:
            parse_expression(
                expression_attribute_values=eav,
                key_condition_expression=kce,
                schema=self.schema,
                expression_attribute_names={},
            )
        assert exc.value.message == "Query condition missed key schema element: job_id"


class TestHashAndRangeKey:
    schema = [
        {"AttributeName": "job_id", "KeyType": "HASH"},
        {"AttributeName": "start_date", "KeyType": "RANGE"},
    ]

    def test_unknown_hash_key(self):
        kce = "wrongName = :id AND start_date = :sk"
        eav = {":id": "pk", ":sk": "sk"}
        with pytest.raises(DynamodbException) as exc:
            parse_expression(
                expression_attribute_values=eav,
                key_condition_expression=kce,
                schema=self.schema,
                expression_attribute_names={},
            )
        assert exc.value.message == "Query condition missed key schema element: job_id"

    @pytest.mark.parametrize(
        "expr",
        [
            "job_id = :id AND wrongName = :sk",
            "job_id = :id AND begins_with ( wrongName , :sk )",
            "job_id = :id AND wrongName BETWEEN :sk and :sk2",
        ],
    )
    def test_unknown_range_key(self, expr):
        eav = {":id": "pk", ":sk": "sk", ":sk2": "sk"}
        with pytest.raises(DynamodbException) as exc:
            parse_expression(
                expression_attribute_values=eav,
                key_condition_expression=expr,
                schema=self.schema,
                expression_attribute_names={},
            )
        assert (
            exc.value.message == "Query condition missed key schema element: start_date"
        )

    @pytest.mark.parametrize(
        "expr",
        [
            "job_id = :id AND begins_with(start_date,:sk)",
            "job_id = :id AND begins_with(start_date, :sk)",
            "job_id = :id AND begins_with( start_date,:sk)",
            "job_id = :id AND begins_with( start_date, :sk)",
            "job_id = :id AND begins_with ( start_date, :sk ) ",
        ],
    )
    def test_begin_with(self, expr):
        eav = {":id": "pk", ":sk": "19"}
        hash_key_conditions, range_key_conditions, _ = parse_expression(
            expression_attribute_values=eav,
            key_condition_expression=expr,
            schema=self.schema,
            expression_attribute_names={},
        )
        assert hash_key_conditions[0][1] == "pk"
        assert range_key_conditions[0][1] == "BEGINS_WITH"
        assert range_key_conditions[0][2] == ["19"]

    @pytest.mark.parametrize("fn", ["Begins_with", "Begins_With", "BEGINS_WITH"])
    def test_begin_with__wrong_case(self, fn):
        eav = {":id": "pk", ":sk": "19"}
        with pytest.raises(DynamodbException) as exc:
            parse_expression(
                expression_attribute_values=eav,
                key_condition_expression=f"job_id = :id AND {fn}(start_date,:sk)",
                schema=self.schema,
                expression_attribute_names={},
            )
        assert (
            exc.value.message
            == f"Invalid KeyConditionExpression: Invalid function name; function: {fn}"
        )

    @pytest.mark.parametrize(
        "expr",
        [
            "job_id = :id and start_date BETWEEN :sk1 AND :sk2",
            "job_id = :id and start_date BETWEEN :sk1 and :sk2",
            "job_id = :id and start_date between :sk1 and :sk2 ",
        ],
    )
    def test_in_between(self, expr):
        eav = {":id": "pk", ":sk1": "19", ":sk2": "21"}
        hash_key_conditions, range_key_conditions, _ = parse_expression(
            expression_attribute_values=eav,
            key_condition_expression=expr,
            schema=self.schema,
            expression_attribute_names={},
        )
        assert hash_key_conditions[0][1] == "pk"
        assert range_key_conditions[0][1] == "BETWEEN"
        assert range_key_conditions[0][2] == ["19", "21"]

    @pytest.mark.parametrize("operator", [" < ", " <=", "= ", ">", ">="])
    def test_numeric_comparisons(self, operator):
        eav = {":id": "pk", ":sk": "19"}
        expr = f"job_id = :id and start_date{operator}:sk"
        hash_key_conditions, range_key_conditions, _ = parse_expression(
            expression_attribute_values=eav,
            key_condition_expression=expr,
            schema=self.schema,
            expression_attribute_names={},
        )
        assert hash_key_conditions[0][1] == "pk"
        assert range_key_conditions[0][1] == operator.strip().upper()
        assert range_key_conditions[0][2] == ["19"]

    @pytest.mark.parametrize(
        "expr",
        [
            "start_date >= :sk and job_id = :id",
            "start_date>:sk and job_id=:id",
            "start_date=:sk and job_id = :id",
            "begins_with(start_date,:sk) and job_id = :id",
        ],
    )
    def test_reverse_keys(self, expr):
        eav = {":id": "pk", ":sk1": "19", ":sk2": "21"}
        hash_key_conditions, range_key_conditions, _ = parse_expression(
            expression_attribute_values=eav,
            key_condition_expression=expr,
            schema=self.schema,
            expression_attribute_names={},
        )
        assert hash_key_conditions[0][1] == "pk"

    @pytest.mark.parametrize(
        "expr",
        [
            "(job_id = :id) and start_date = :sk",
            "job_id = :id and (start_date = :sk)",
            "(job_id = :id) and (start_date = :sk)",
        ],
    )
    def test_brackets(self, expr):
        hash_key_conditions, range_key_conditions, _ = parse_expression(
            expression_attribute_values={":id": "pk", ":sk": "19"},
            key_condition_expression=expr,
            schema=self.schema,
            expression_attribute_names={},
        )
        assert hash_key_conditions[0][1] == "pk"


class TestNamesAndValues:
    schema = [{"AttributeName": "job_id", "KeyType": "HASH"}]

    def test_names_and_values(self):
        kce = ":j = :id"
        ean = {":j": "job_id"}
        eav = {":id": {"S": "asdasdasd"}}
        hash_key_conditions, range_key_conditions, _ = parse_expression(
            expression_attribute_values=eav,
            key_condition_expression=kce,
            schema=self.schema,
            expression_attribute_names=ean,
        )
        assert hash_key_conditions[0][1] == eav[":id"]
        assert range_key_conditions == []


def test_expression_attribute_names_found():
    kce = ":j = :id"
    ean = {":j": "job_id"}
    eav = {":id": {"S": "asdasdasd"}}
    hash_key_conditions, range_key_conditions, expression_attribute_names_used = (
        parse_expression(
            expression_attribute_values=eav,
            key_condition_expression=kce,
            schema=[{"AttributeName": "job_id", "KeyType": "HASH"}],
            expression_attribute_names=ean,
        )
    )
    assert expression_attribute_names_used == [":j"]
