import unittest

from lark.exceptions import VisitError

from optimade.filterparser import LarkParser, ParserError
from optimade.filtertransformers.mongo import MongoTransformer
from optimade.server.mappers import BaseResourceMapper


class TestMongoTransformer(unittest.TestCase):
    version = (0, 10, 1)
    variant = "default"

    def setUp(self):
        p = LarkParser(version=self.version, variant=self.variant)
        t = MongoTransformer()
        self.transform = lambda inp: t.transform(p.parse(inp))

    def test_empty(self):
        self.assertIsNone(self.transform(" "))

    def test_property_names(self):
        self.assertEqual(self.transform("band_gap = 1"), {"band_gap": {"$eq": 1}})
        self.assertEqual(
            self.transform("cell_length_a = 1"), {"cell_length_a": {"$eq": 1}}
        )
        self.assertEqual(self.transform("cell_volume = 1"), {"cell_volume": {"$eq": 1}})

        with self.assertRaises(ParserError):
            self.transform("0_kvak IS KNOWN")  # starts with a number

        with self.assertRaises(ParserError):
            self.transform('"foo bar" IS KNOWN')  # contains space; contains quotes

        with self.assertRaises(ParserError):
            self.transform("BadLuck IS KNOWN")  # contains upper-case letters

        # database-provider-specific prefixes
        self.assertEqual(
            self.transform("_exmpl_formula_sum = 1"), {"_exmpl_formula_sum": {"$eq": 1}}
        )
        self.assertEqual(
            self.transform("_exmpl_band_gap = 1"), {"_exmpl_band_gap": {"$eq": 1}}
        )

        # Nested property names
        self.assertEqual(
            self.transform("identifier1.identifierd2 = 42"),
            {"identifier1.identifierd2": {"$eq": 42}},
        )

    def test_string_values(self):
        self.assertEqual(
            self.transform('author="Sąžininga Žąsis"'),
            {"author": {"$eq": "Sąžininga Žąsis"}},
        )
        self.assertEqual(
            self.transform('field = "!#$%&\'() * +, -./:; <= > ? @[] ^ `{|}~ % "'),
            {"field": {"$eq": "!#$%&'() * +, -./:; <= > ? @[] ^ `{|}~ % "}},
        )

    def test_number_values(self):
        self.assertEqual(self.transform("a = 12345"), {"a": {"$eq": 12345}})
        self.assertEqual(self.transform("b = +12"), {"b": {"$eq": 12}})
        self.assertEqual(self.transform("c = -34"), {"c": {"$eq": -34}})
        self.assertEqual(self.transform("d = 1.2"), {"d": {"$eq": 1.2}})
        self.assertEqual(self.transform("e = .2E7"), {"e": {"$eq": 2000000.0}})
        self.assertEqual(self.transform("f = -.2E+7"), {"f": {"$eq": -2000000.0}})
        self.assertEqual(self.transform("g = +10.01E-10"), {"g": {"$eq": 1.001e-09}})
        self.assertEqual(self.transform("h = 6.03e23"), {"h": {"$eq": 6.03e23}})
        self.assertEqual(self.transform("i = .1E1"), {"i": {"$eq": 1.0}})
        self.assertEqual(self.transform("j = -.1e1"), {"j": {"$eq": -1.0}})
        self.assertEqual(self.transform("k = 1.e-12"), {"k": {"$eq": 1e-12}})
        self.assertEqual(self.transform("l = -.1e-12"), {"l": {"$eq": -1e-13}})
        self.assertEqual(
            self.transform("m = 1000000000.E1000000000"), {"m": {"$eq": float("inf")}}
        )

        with self.assertRaises(ParserError):
            self.transform("number=1.234D12")
        with self.assertRaises(ParserError):
            self.transform("number=.e1")
        with self.assertRaises(ParserError):
            self.transform("number= -.E1")
        with self.assertRaises(ParserError):
            self.transform("number=+.E2")
        with self.assertRaises(ParserError):
            self.transform("number=1.23E+++")
        with self.assertRaises(ParserError):
            self.transform("number=+-123")
        with self.assertRaises(ParserError):
            self.transform("number=0.0.1")

    def test_simple_comparisons(self):
        self.assertEqual(self.transform("a<3"), {"a": {"$lt": 3}})
        self.assertEqual(self.transform("a<=3"), {"a": {"$lte": 3}})
        self.assertEqual(self.transform("a>3"), {"a": {"$gt": 3}})
        self.assertEqual(self.transform("a>=3"), {"a": {"$gte": 3}})
        self.assertEqual(self.transform("a=3"), {"a": {"$eq": 3}})
        self.assertEqual(self.transform("a!=3"), {"a": {"$ne": 3}})

    def test_id(self):
        self.assertEqual(self.transform('id="example/1"'), {"id": {"$eq": "example/1"}})
        self.assertEqual(
            self.transform('"example/1" = id'), {"id": {"$eq": "example/1"}}
        )
        self.assertEqual(
            self.transform('id="test/2" OR "example/1" = id'),
            {"$or": [{"id": {"$eq": "test/2"}}, {"id": {"$eq": "example/1"}}]},
        )

    def test_operators(self):
        # Basic boolean operations
        # TODO: {"a": {"$not": {"$lt": 3}}} can be simplified to {"a": {"$gte": 3}}
        self.assertEqual(self.transform("NOT a<3"), {"a": {"$not": {"$lt": 3}}})

        # TODO: {'$not': {'$eq': 'Ti'}} can be simplified to {'$ne': 'Ti'}
        self.assertEqual(
            self.transform(
                "NOT ( "
                'chemical_formula_hill = "Al" AND chemical_formula_anonymous = "A" OR '
                'chemical_formula_anonymous = "H2O" AND NOT chemical_formula_hill = "Ti" '
                ")"
            ),
            {
                "$nor": [
                    {
                        "$and": [
                            {"chemical_formula_hill": {"$eq": "Al"}},
                            {"chemical_formula_anonymous": {"$eq": "A"}},
                        ]
                    },
                    {
                        "$and": [
                            {"chemical_formula_anonymous": {"$eq": "H2O"}},
                            {"chemical_formula_hill": {"$not": {"$eq": "Ti"}}},
                        ]
                    },
                ]
            },
        )

        # Numeric and String comparisons
        self.assertEqual(self.transform("nelements > 3"), {"nelements": {"$gt": 3}})
        self.assertEqual(
            self.transform(
                'chemical_formula_hill = "H2O" AND chemical_formula_anonymous != "AB"'
            ),
            {
                "$and": [
                    {"chemical_formula_hill": {"$eq": "H2O"}},
                    {"chemical_formula_anonymous": {"$ne": "AB"}},
                ]
            },
        )
        self.assertEqual(
            self.transform(
                "_exmpl_aax <= +.1e8 OR nelements >= 10 AND "
                'NOT ( _exmpl_x != "Some string" OR NOT _exmpl_a = 7)'
            ),
            {
                "$or": [
                    {"_exmpl_aax": {"$lte": 10000000.0}},
                    {
                        "$and": [
                            {"nelements": {"$gte": 10}},
                            {
                                "$nor": [
                                    {"_exmpl_x": {"$ne": "Some string"}},
                                    {"_exmpl_a": {"$not": {"$eq": 7}}},
                                ]
                            },
                        ]
                    },
                ]
            },
        )
        self.assertEqual(
            self.transform('_exmpl_spacegroup="P2"'),
            {"_exmpl_spacegroup": {"$eq": "P2"}},
        )
        self.assertEqual(
            self.transform("_exmpl_cell_volume<100.0"),
            {"_exmpl_cell_volume": {"$lt": 100.0}},
        )
        self.assertEqual(
            self.transform("_exmpl_bandgap > 5.0 AND _exmpl_molecular_weight < 350"),
            {
                "$and": [
                    {"_exmpl_bandgap": {"$gt": 5.0}},
                    {"_exmpl_molecular_weight": {"$lt": 350}},
                ]
            },
        )
        self.assertEqual(
            self.transform(
                '_exmpl_melting_point<300 AND nelements=4 AND elements="Si,O2"'
            ),
            {
                "$and": [
                    {"_exmpl_melting_point": {"$lt": 300}},
                    {"nelements": {"$eq": 4}},
                    {"elements": {"$eq": "Si,O2"}},
                ]
            },
        )
        self.assertEqual(
            self.transform("_exmpl_some_string_property = 42"),
            {"_exmpl_some_string_property": {"$eq": 42}},
        )
        self.assertEqual(self.transform("5 < _exmpl_a"), {"_exmpl_a": {"$gt": 5}})

        self.assertEqual(
            self.transform("a<5 AND b=0"),
            {"$and": [{"a": {"$lt": 5}}, {"b": {"$eq": 0}}]},
        )
        self.assertEqual(
            self.transform("a >= 8 OR a<5 AND b>=8"),
            {
                "$or": [
                    {"a": {"$gte": 8}},
                    {"$and": [{"a": {"$lt": 5}}, {"b": {"$gte": 8}}]},
                ]
            },
        )

        # OPTIONAL
        # self.assertEqual(self.transform("((NOT (_exmpl_a>_exmpl_b)) AND _exmpl_x>0)"), {})

        self.assertEqual(
            self.transform("NOT (a>1 AND b>1)"),
            {"$and": [{"a": {"$not": {"$gt": 1}}}, {"b": {"$not": {"$gt": 1}}}]},
        )

        self.assertEqual(
            self.transform("NOT (a>1 AND b>1 OR c>1)"),
            {
                "$nor": [
                    {"$and": [{"a": {"$gt": 1}}, {"b": {"$gt": 1}}]},
                    {"c": {"$gt": 1}},
                ]
            },
        )

        self.assertEqual(
            self.transform("NOT (a>1 AND ( b>1 OR c>1 ))"),
            {
                "$and": [
                    {"a": {"$not": {"$gt": 1}}},
                    {"$nor": [{"b": {"$gt": 1}}, {"c": {"$gt": 1}}]},
                ]
            },
        )

        self.assertEqual(
            self.transform("NOT (a>1 AND ( b>1 OR (c>1 AND d>1 ) ))"),
            {
                "$and": [
                    {"a": {"$not": {"$gt": 1}}},
                    {
                        "$nor": [
                            {"b": {"$gt": 1}},
                            {"$and": [{"c": {"$gt": 1}}, {"d": {"$gt": 1}}]},
                        ]
                    },
                ]
            },
        )

        self.assertEqual(
            self.transform(
                'elements HAS "Ag" AND NOT ( elements HAS "Ir" AND elements HAS "Ac" )'
            ),
            {
                "$and": [
                    {"elements": {"$in": ["Ag"]}},
                    {
                        "$and": [
                            {"elements": {"$not": {"$in": ["Ir"]}}},
                            {"elements": {"$not": {"$in": ["Ac"]}}},
                        ]
                    },
                ]
            },
        )

        self.assertEqual(self.transform("5 < 7"), {7: {"$gt": 5}})

        with self.assertRaises(VisitError):
            self.transform('"some string" > "some other string"')

    def test_filtering_on_relationships(self):
        """ Test the nested properties with special names
        like "structures", "references" etc. are applied
        to the relationships field.

        """

        self.assertEqual(
            self.transform('references.id HAS "dummy/2019"'),
            {"relationships.references.data.id": {"$in": ["dummy/2019"]}},
        )

        self.assertEqual(
            self.transform('structures.id HAS ANY "dummy/2019", "dijkstra1968"'),
            {
                "relationships.structures.data.id": {
                    "$in": ["dummy/2019", "dijkstra1968"]
                }
            },
        )

        self.assertEqual(
            self.transform('structures.id HAS ALL "dummy/2019", "dijkstra1968"'),
            {
                "relationships.structures.data.id": {
                    "$all": ["dummy/2019", "dijkstra1968"]
                }
            },
        )

        self.assertEqual(
            self.transform('structures.id HAS ONLY "dummy/2019"'),
            {
                "$and": [
                    {"relationships.structures.data": {"$size": 1}},
                    {"relationships.structures.data.id": {"$all": ["dummy/2019"]}},
                ]
            },
        )

        self.assertEqual(
            self.transform(
                'structures.id HAS ONLY "dummy/2019" AND structures.id HAS "dummy/2019"'
            ),
            {
                "$and": [
                    {
                        "$and": [
                            {"relationships.structures.data": {"$size": 1}},
                            {
                                "relationships.structures.data.id": {
                                    "$all": ["dummy/2019"]
                                }
                            },
                        ]
                    },
                    {"relationships.structures.data.id": {"$in": ["dummy/2019"]}},
                ],
            },
        )

    def test_not_implemented(self):
        """ Test that list properties that are currently not implemented
        give a sensible response.

        """
        # NOTE: Lark catches underlying filtertransformer exceptions and
        # raises VisitErrors, most of these actually correspond to NotImplementedError
        with self.assertRaises(VisitError):
            try:
                self.transform("list HAS < 3")
            except Exception as exc:
                self.assertTrue("not implemented" in str(exc))
                raise exc

        with self.assertRaises(VisitError):
            try:
                self.transform("list HAS ALL < 3, > 3")
            except Exception as exc:
                self.assertTrue("not implemented" in str(exc))
                raise exc

        with self.assertRaises(VisitError):
            try:
                self.transform("list HAS ANY > 3, < 6")
            except Exception as exc:
                self.assertTrue("not implemented" in str(exc))
                raise exc

        self.assertEqual(self.transform("list LENGTH 3"), {"list": {"$size": 3}})

        with self.assertRaises(VisitError):
            self.transform("list:list HAS >=2:<=5")

        with self.assertRaises(VisitError):
            self.transform(
                'elements:_exmpl_element_counts HAS "H":6 AND elements:_exmpl_element_counts '
                'HAS ALL "H":6,"He":7 AND elements:_exmpl_element_counts HAS ONLY "H":6 AND '
                'elements:_exmpl_element_counts HAS ANY "H":6,"He":7 AND '
                'elements:_exmpl_element_counts HAS ONLY "H":6,"He":7'
            )

        with self.assertRaises(VisitError):
            self.transform(
                "_exmpl_element_counts HAS < 3 AND _exmpl_element_counts "
                "HAS ANY > 3, = 6, 4, != 8"
            )

        with self.assertRaises(VisitError):
            self.transform(
                "elements:_exmpl_element_counts:_exmpl_element_weights "
                'HAS ANY > 3:"He":>55.3 , = 6:>"Ti":<37.6 , 8:<"Ga":0'
            )

        self.assertEqual(
            self.transform("list LENGTH > 3"), {"list.4": {"$exists": True}}
        )

    def test_list_length_aliases(self):
        from optimade.server.mappers import StructureMapper

        transformer = MongoTransformer(mapper=StructureMapper())
        parser = LarkParser(version=self.version, variant=self.variant)

        self.assertEqual(
            transformer.transform(parser.parse("elements LENGTH 3")), {"nelements": 3}
        )

        self.assertEqual(
            transformer.transform(
                parser.parse('elements HAS "Li" AND elements LENGTH = 3')
            ),
            {"$and": [{"elements": {"$in": ["Li"]}}, {"nelements": 3}]},
        )

        self.assertEqual(
            transformer.transform(parser.parse("elements LENGTH > 3")),
            {"nelements": {"$gt": 3}},
        )
        self.assertEqual(
            transformer.transform(parser.parse("elements LENGTH < 3")),
            {"nelements": {"$lt": 3}},
        )
        self.assertEqual(
            transformer.transform(parser.parse("elements LENGTH = 3")), {"nelements": 3}
        )
        self.assertEqual(
            transformer.transform(parser.parse("cartesian_site_positions LENGTH <= 3")),
            {"nsites": {"$lte": 3}},
        )
        self.assertEqual(
            transformer.transform(parser.parse("cartesian_site_positions LENGTH >= 3")),
            {"nsites": {"$gte": 3}},
        )

    def test_unaliased_length_operator(self):
        self.assertEqual(
            self.transform("cartesian_site_positions LENGTH <= 3"),
            {"cartesian_site_positions.4": {"$exists": False}},
        )
        self.assertEqual(
            self.transform("cartesian_site_positions LENGTH < 3"),
            {"cartesian_site_positions.3": {"$exists": False}},
        )
        self.assertEqual(
            self.transform("cartesian_site_positions LENGTH 3"),
            {"cartesian_site_positions": {"$size": 3}},
        )
        self.assertEqual(
            self.transform("cartesian_site_positions LENGTH >= 10"),
            {"cartesian_site_positions.10": {"$exists": True}},
        )

        self.assertEqual(
            self.transform("cartesian_site_positions LENGTH > 10"),
            {"cartesian_site_positions.11": {"$exists": True}},
        )

    def test_aliased_length_operator(self):
        from optimade.server.mappers import StructureMapper

        class MyMapper(StructureMapper):
            ALIASES = (("elements", "my_elements"), ("nelements", "nelem"))
            LENGTH_ALIASES = (
                ("chemsys", "nelements"),
                ("cartesian_site_positions", "nsites"),
                ("elements", "nelements"),
            )
            PROVIDER_FIELDS = ("chemsys",)

        transformer = MongoTransformer(mapper=MyMapper())
        parser = LarkParser(version=self.version, variant=self.variant)

        self.assertEqual(
            transformer.transform(parser.parse("cartesian_site_positions LENGTH <= 3")),
            {"nsites": {"$lte": 3}},
        )
        self.assertEqual(
            transformer.transform(parser.parse("cartesian_site_positions LENGTH < 3")),
            {"nsites": {"$lt": 3}},
        )
        self.assertEqual(
            transformer.transform(parser.parse("cartesian_site_positions LENGTH 3")),
            {"nsites": 3},
        )
        self.assertEqual(
            transformer.transform(parser.parse("cartesian_site_positions LENGTH 3")),
            {"nsites": 3},
        )
        self.assertEqual(
            transformer.transform(
                parser.parse("cartesian_site_positions LENGTH >= 10")
            ),
            {"nsites": {"$gte": 10}},
        )

        self.assertEqual(
            transformer.transform(parser.parse("structure_features LENGTH > 10")),
            {"structure_features.11": {"$exists": True}},
        )

        self.assertEqual(
            transformer.transform(parser.parse("nsites LENGTH > 10")),
            {"nsites.11": {"$exists": True}},
        )

        self.assertEqual(
            transformer.transform(parser.parse("elements LENGTH 3")), {"nelem": 3},
        )

        self.assertEqual(
            transformer.transform(parser.parse('elements HAS "Ag"')),
            {"my_elements": {"$in": ["Ag"]}},
        )

        self.assertEqual(
            transformer.transform(parser.parse("chemsys LENGTH 3")), {"nelem": 3},
        )

    def test_aliases(self):
        """ Test that valid aliases are allowed, but do not effect
        r-values.

        """

        class MyStructureMapper(BaseResourceMapper):
            ALIASES = (
                ("elements", "my_elements"),
                ("A", "D"),
                ("B", "E"),
                ("C", "F"),
                ("_exmpl_nested_field", "nested_field"),
            )

        mapper = MyStructureMapper()
        t = MongoTransformer(mapper=mapper)

        self.assertEqual(mapper.alias_for("elements"), "my_elements")

        test_filter = {"elements": {"$in": ["A", "B", "C"]}}
        self.assertEqual(
            t.postprocess(test_filter), {"my_elements": {"$in": ["A", "B", "C"]}},
        )
        test_filter = {"$and": [{"elements": {"$in": ["A", "B", "C"]}}]}
        self.assertEqual(
            t.postprocess(test_filter),
            {"$and": [{"my_elements": {"$in": ["A", "B", "C"]}}]},
        )
        test_filter = {"elements": "A"}
        self.assertEqual(t.postprocess(test_filter), {"my_elements": "A"})
        test_filter = ["A", "B", "C"]
        self.assertEqual(t.postprocess(test_filter), ["A", "B", "C"])

        test_filter = ["A", "elements", "C"]
        self.assertEqual(t.postprocess(test_filter), ["A", "elements", "C"])

        test_filter = {"_exmpl_nested_field.sub_property": {"$gt": 1234.5}}
        self.assertEqual(
            t.postprocess(test_filter), {"nested_field.sub_property": {"$gt": 1234.5}}
        )

        test_filter = {"_exmpl_nested_field.sub_property.x": {"$exists": False}}
        self.assertEqual(
            t.postprocess(test_filter),
            {"nested_field.sub_property.x": {"$exists": False}},
        )

    def test_list_properties(self):
        """ Test the HAS ALL, ANY and optional ONLY queries.

        """
        self.assertEqual(
            self.transform('elements HAS ONLY "H","He","Ga","Ta"'),
            {"elements": {"$all": ["H", "He", "Ga", "Ta"], "$size": 4}},
        )

        self.assertEqual(
            self.transform('elements HAS ANY "H","He","Ga","Ta"'),
            {"elements": {"$in": ["H", "He", "Ga", "Ta"]}},
        )

        self.assertEqual(
            self.transform('elements HAS ALL "H","He","Ga","Ta"'),
            {"elements": {"$all": ["H", "He", "Ga", "Ta"]}},
        )

        self.assertEqual(
            self.transform(
                'elements HAS "H" AND elements HAS ALL "H","He","Ga","Ta" AND elements HAS '
                'ONLY "H","He","Ga","Ta" AND elements HAS ANY "H", "He", "Ga", "Ta"'
            ),
            {
                "$and": [
                    {"elements": {"$in": ["H"]}},
                    {"elements": {"$all": ["H", "He", "Ga", "Ta"]}},
                    {"elements": {"$all": ["H", "He", "Ga", "Ta"], "$size": 4}},
                    {"elements": {"$in": ["H", "He", "Ga", "Ta"]}},
                ]
            },
        )

    def test_known_properties(self):
        #  Filtering on Properties with unknown value
        self.assertEqual(
            self.transform("chemical_formula_anonymous IS UNKNOWN"),
            {
                "$or": [
                    {"chemical_formula_anonymous": {"$exists": False}},
                    {"chemical_formula_anonymous": {"$eq": None}},
                ]
            },
        )
        self.assertEqual(
            self.transform("chemical_formula_anonymous IS KNOWN"),
            {
                "$and": [
                    {"chemical_formula_anonymous": {"$exists": True}},
                    {"chemical_formula_anonymous": {"$ne": None}},
                ]
            },
        )
        self.assertEqual(
            self.transform("NOT chemical_formula_anonymous IS UNKNOWN"),
            {
                "$and": [
                    {"chemical_formula_anonymous": {"$exists": True}},
                    {"chemical_formula_anonymous": {"$ne": None}},
                ]
            },
        )
        self.assertEqual(
            self.transform("NOT chemical_formula_anonymous IS KNOWN"),
            {
                "$or": [
                    {"chemical_formula_anonymous": {"$exists": False}},
                    {"chemical_formula_anonymous": {"$eq": None}},
                ]
            },
        )

        self.assertEqual(
            self.transform(
                "chemical_formula_hill IS KNOWN AND NOT chemical_formula_anonymous IS UNKNOWN"
            ),
            {
                "$and": [
                    {
                        "$and": [
                            {"chemical_formula_hill": {"$exists": True}},
                            {"chemical_formula_hill": {"$ne": None}},
                        ]
                    },
                    {
                        "$and": [
                            {"chemical_formula_anonymous": {"$exists": True}},
                            {"chemical_formula_anonymous": {"$ne": None}},
                        ]
                    },
                ]
            },
        )

        self.assertEqual(
            self.transform(
                "chemical_formula_hill IS KNOWN AND chemical_formula_anonymous IS UNKNOWN"
            ),
            {
                "$and": [
                    {
                        "$and": [
                            {"chemical_formula_hill": {"$exists": True}},
                            {"chemical_formula_hill": {"$ne": None}},
                        ]
                    },
                    {
                        "$or": [
                            {"chemical_formula_anonymous": {"$exists": False}},
                            {"chemical_formula_anonymous": {"$eq": None}},
                        ]
                    },
                ]
            },
        )

    def test_precedence(self):
        self.assertEqual(
            self.transform('NOT a > b OR c = 100 AND f = "C2 H6"'),
            {
                "$or": [
                    {"a": {"$not": {"$gt": "b"}}},
                    {"$and": [{"c": {"$eq": 100}}, {"f": {"$eq": "C2 H6"}}]},
                ]
            },
        )
        self.assertEqual(
            self.transform('NOT a > b OR c = 100 AND f = "C2 H6"'),
            self.transform('(NOT (a > b)) OR ( (c = 100) AND (f = "C2 H6") )'),
        )
        self.assertEqual(
            self.transform("a >= 0 AND NOT b < c OR c = 0"),
            self.transform("((a >= 0) AND (NOT (b < c))) OR (c = 0)"),
        )

    def test_special_cases(self):
        self.assertEqual(self.transform("te < st"), {"te": {"$lt": "st"}})
        self.assertEqual(
            self.transform('spacegroup="P2"'), {"spacegroup": {"$eq": "P2"}}
        )
        self.assertEqual(
            self.transform("_cod_cell_volume<100.0"),
            {"_cod_cell_volume": {"$lt": 100.0}},
        )
        self.assertEqual(
            self.transform("_mp_bandgap > 5.0 AND _cod_molecular_weight < 350"),
            {
                "$and": [
                    {"_mp_bandgap": {"$gt": 5.0}},
                    {"_cod_molecular_weight": {"$lt": 350}},
                ]
            },
        )
        self.assertEqual(
            self.transform(
                '_cod_melting_point<300 AND nelements=4 AND elements="Si,O2"'
            ),
            {
                "$and": [
                    {"_cod_melting_point": {"$lt": 300}},
                    {"nelements": {"$eq": 4}},
                    {"elements": {"$eq": "Si,O2"}},
                ]
            },
        )
        self.assertEqual(self.transform("key=value"), {"key": {"$eq": "value"}})
        self.assertEqual(
            self.transform('author=" someone "'), {"author": {"$eq": " someone "}}
        )
        self.assertEqual(self.transform("notice=val"), {"notice": {"$eq": "val"}})
        self.assertEqual(
            self.transform("NOTice=val"), {"ice": {"$not": {"$eq": "val"}}}
        )
        self.assertEqual(
            self.transform(
                "number=0.ANDnumber=.0ANDnumber=0.0ANDnumber=+0AND_n_u_m_b_e_r_=-0AND"
                "number=0e1ANDnumber=0e-1ANDnumber=0e+1"
            ),
            {
                "$and": [
                    {"number": {"$eq": 0.0}},
                    {"number": {"$eq": 0.0}},
                    {"number": {"$eq": 0.0}},
                    {"number": {"$eq": 0}},
                    {"_n_u_m_b_e_r_": {"$eq": 0}},
                    {"number": {"$eq": 0.0}},
                    {"number": {"$eq": 0.0}},
                    {"number": {"$eq": 0.0}},
                ]
            },
        )
