# Copyright (C) 2023, CERN
# This software is distributed under the terms of the MIT
# licence, copied verbatim in the file "LICENSE".
# In applying this license, CERN does not waive the privileges and immunities
# granted to it by virtue of its status as Intergovernmental Organization
# or submit itself to any jurisdiction.

import parsley
import pytest

from simple_repository_browser import _search
from simple_repository_browser._search import Filter, FilterOn


@pytest.mark.parametrize(
    ["query", "expected_expression_graph"],
    [
        ("", ()),
        pytest.param("some-name", (Filter(FilterOn.name_or_summary, 'some-name'),)),
        pytest.param("some name", (_search.And(Filter(FilterOn.name_or_summary, 'some'), Filter(FilterOn.name_or_summary, 'name')),)),
        pytest.param("som*name", (Filter(FilterOn.name_or_summary, 'som*name'),)),
        pytest.param('"some name"', (Filter(FilterOn.name_or_summary, '"some name"'),)),
        pytest.param('"some-name"', (Filter(FilterOn.name_or_summary, '"some-name"'),)),
        pytest.param('"CASE"', (Filter(FilterOn.name_or_summary, '"CASE"'),)),
        pytest.param('-foo', (_search.Not(Filter(FilterOn.name_or_summary, 'foo')),)),
        pytest.param('-"foo bar"', (_search.Not(Filter(FilterOn.name_or_summary, '"foo bar"')),)),
        pytest.param('-name:"foo bar"', (_search.Not(Filter(FilterOn.name, '"foo bar"')),)),
        pytest.param('name:foo', (Filter(FilterOn.name, 'foo'),)),
        pytest.param(
            'name:foo OR name:bar', (
                _search.Or(
                    Filter(FilterOn.name, 'foo'),
                    Filter(FilterOn.name, 'bar'),
                ),
            ),
        ),
        pytest.param(
            'name:foo AND "fiddle AND sticks"', (
                _search.And(
                    Filter(FilterOn.name, 'foo'),
                    Filter(FilterOn.name_or_summary, '"fiddle AND sticks"'),
                ),
            ),
        ),
        pytest.param('summary:foo', (Filter(FilterOn.summary, 'foo'),)),
        pytest.param(
            'name:"NAME OR" AND "fiddle AND sticks"', (
                _search.And(
                    Filter(FilterOn.name, '"NAME OR"'),
                    Filter(FilterOn.name_or_summary, '"fiddle AND sticks"'),
                ),
            ),
        ),
        pytest.param('(((a)))', (Filter(FilterOn.name_or_summary, 'a'),)),
        pytest.param('(((a) OR (b)))', (_search.Or(Filter(FilterOn.name_or_summary, 'a'), Filter(FilterOn.name_or_summary, 'b')),)),
        pytest.param(
            '(a AND b) OR (c AND d)', (
                _search.Or(
                    _search.And(Filter(FilterOn.name_or_summary, 'a'), Filter(FilterOn.name_or_summary, 'b')),
                    _search.And(Filter(FilterOn.name_or_summary, 'c'), Filter(FilterOn.name_or_summary, 'd')),
                ),
            ),
        ),
        pytest.param(
            '((a AND b)) OR (c AND -d)', (
                _search.Or(
                    _search.And(Filter(FilterOn.name_or_summary, 'a'), Filter(FilterOn.name_or_summary, 'b')),
                    _search.And(Filter(FilterOn.name_or_summary, 'c'), _search.Not(Filter(FilterOn.name_or_summary, 'd'))),
                ),
            ),
        ),
    ],
)
def test_parse_query(query, expected_expression_graph):
    result = _search.parse(query)
    assert result == expected_expression_graph


@pytest.mark.parametrize(
    ["query", "expected_result"],
    [
        ("", None),
        ("name:foo", "foo"),
        ("name:foo__unnormed", "foo-unnormed"),
        ("foo", "foo"),
        ("some*.Name", None),
        ("summary:\"Some Description\"", None),
        ("foo bar", None),
        ("foo OR bar", None),
        ("-name:foo OR -bar", None),
    ],
)
def test_simple_name_proposal(query, expected_result):
    terms = _search.parse(query)
    result = _search.simple_name_from_query(terms)
    assert result == expected_result


@pytest.mark.parametrize(
    ["query", "expected_predicate"],
    [
        ("", ("", ())),
        ("name:foo", ('canonical_name LIKE ?', ('%foo%',))),
        ("name:foo__unnormed", ('canonical_name LIKE ?', ('%foo-unnormed%',))),
        ("foo", ('(canonical_name LIKE ? OR summary LIKE ?)', ('%foo%', '%foo%'))),
        ("some*.Name", ('(canonical_name LIKE ? OR summary LIKE ?)', ('%some%-name%', '%some%.Name%'))),
        ("summary:\"Some Description\"", ('summary LIKE ?', ('%Some Description%',))),
        ("foo bar", ('((canonical_name LIKE ? OR summary LIKE ?) AND (canonical_name LIKE ? OR summary LIKE ?))', ('%foo%', '%foo%', '%bar%', '%bar%'))),
        ("foo OR bar", ('((canonical_name LIKE ? OR summary LIKE ?) OR (canonical_name LIKE ? OR summary LIKE ?))', ('%foo%', '%foo%', '%bar%', '%bar%'))),
        ("-name:foo OR -bar", ('(Not (canonical_name LIKE ? OR (Not (canonical_name LIKE ? OR summary LIKE ?))))', ('%foo%', '%bar%', '%bar%'))),
        ("summary:\"Some'; DROP TABLE gotcha; ' Description\"", ('summary LIKE ?', ("%Some'; DROP TABLE gotcha; ' Description%",))),
    ],
)
def test_build_sql_predicate(query, expected_predicate):
    sql_stmt, params = _search.query_to_sql(query)
    assert (sql_stmt, params) == expected_predicate
    assert sql_stmt == expected_predicate[0]
    assert params == expected_predicate[1]


@pytest.mark.parametrize(
    ["query", "expected_exception"],
    [
        # ("", ()),   ? Should this be an error? Currently explicitly enabled.
        (" ", pytest.raises(parsley.ParseError)),
        ("'s'", pytest.raises(parsley.ParseError)),
        ("\"imbalanced", pytest.raises(parsley.ParseError)),
        ("unacceptable;char", pytest.raises(parsley.ParseError)),
        ("unacceptable%char", pytest.raises(parsley.ParseError)),
        ("-name:(foo OR bar)", pytest.raises(parsley.ParseError)),
        ("name:", pytest.raises(parsley.ParseError)),
        ('notallowed:foo', pytest.raises(parsley.ParseError)),
    ],
)
def test_invalid_query(query, expected_exception):
    with expected_exception:
        result = _search.parse(query)
        print('Result:', result)
