"""Tests for rbsp.query.parser -- QueryParser, ParsedQuery, QueryType."""

import os
import sys
import unittest

sys.path.insert(0, os.path.dirname(__file__))
from conftest import RBSPTestCase, FIXED_TIMESTAMP, SAMPLE_TEXTS, SAMPLE_CODE, SAMPLE_PROSE

from rbsp.query.parser import QueryParser, QueryType, TokenType, ParsedQuery, QueryToken


class TestQueryParserKeywords(unittest.TestCase):
    """Test basic keyword parsing."""

    def setUp(self):
        self.parser = QueryParser()

    def test_single_keyword(self):
        result = self.parser.parse("hello")
        self.assertEqual(result.query_type, QueryType.KEYWORD)
        self.assertEqual(result.terms, ("hello",))
        self.assertEqual(result.raw_query, "hello")

    def test_multiple_keywords(self):
        result = self.parser.parse("hello world")
        self.assertEqual(result.query_type, QueryType.KEYWORD)
        self.assertEqual(result.terms, ("hello", "world"))

    def test_keywords_lowercased(self):
        result = self.parser.parse("Hello WORLD")
        self.assertEqual(result.terms, ("hello", "world"))

    def test_keyword_tokens(self):
        result = self.parser.parse("foo bar")
        self.assertEqual(len(result.tokens), 2)
        self.assertEqual(result.tokens[0].token_type, TokenType.TERM)
        self.assertEqual(result.tokens[0].value, "foo")
        self.assertEqual(result.tokens[1].token_type, TokenType.TERM)
        self.assertEqual(result.tokens[1].value, "bar")


class TestQueryParserPhrases(unittest.TestCase):
    """Test quoted phrase parsing."""

    def setUp(self):
        self.parser = QueryParser()

    def test_double_quoted_phrase(self):
        result = self.parser.parse('"exact phrase"')
        self.assertEqual(result.query_type, QueryType.PHRASE)
        self.assertEqual(result.phrases, ("exact phrase",))

    def test_single_quoted_phrase(self):
        result = self.parser.parse("'another phrase'")
        self.assertEqual(result.query_type, QueryType.PHRASE)
        self.assertEqual(result.phrases, ("another phrase",))

    def test_phrase_token_type(self):
        result = self.parser.parse('"hello world"')
        self.assertEqual(len(result.tokens), 1)
        self.assertEqual(result.tokens[0].token_type, TokenType.PHRASE)
        self.assertEqual(result.tokens[0].value, "hello world")

    def test_phrase_only_is_phrase_type(self):
        result = self.parser.parse('"only a phrase"')
        self.assertEqual(result.query_type, QueryType.PHRASE)

    def test_phrase_with_keywords_is_keyword_type(self):
        result = self.parser.parse('"a phrase" keyword')
        self.assertEqual(result.query_type, QueryType.KEYWORD)
        self.assertEqual(result.phrases, ("a phrase",))
        self.assertEqual(result.terms, ("keyword",))


class TestQueryParserBoolean(unittest.TestCase):
    """Test boolean operator parsing."""

    def setUp(self):
        self.parser = QueryParser()

    def test_and_operator(self):
        result = self.parser.parse("foo AND bar")
        self.assertEqual(result.query_type, QueryType.BOOLEAN)
        self.assertEqual(result.terms, ("foo", "bar"))
        self.assertTrue(any(t.token_type == TokenType.AND for t in result.tokens))

    def test_or_operator(self):
        result = self.parser.parse("foo OR bar")
        self.assertEqual(result.query_type, QueryType.BOOLEAN)
        self.assertTrue(any(t.token_type == TokenType.OR for t in result.tokens))

    def test_not_operator_excludes_next(self):
        result = self.parser.parse("foo NOT bar")
        self.assertEqual(result.query_type, QueryType.BOOLEAN)
        self.assertEqual(result.terms, ("foo",))
        self.assertEqual(result.excluded, ("bar",))

    def test_not_with_phrase(self):
        result = self.parser.parse('foo NOT "some phrase"')
        self.assertEqual(result.excluded, ("some phrase",))
        self.assertNotIn("some phrase", result.phrases)

    def test_boolean_overrides_other_types(self):
        result = self.parser.parse("~semantic AND keyword")
        self.assertEqual(result.query_type, QueryType.BOOLEAN)


class TestQueryParserSemantic(unittest.TestCase):
    """Test semantic (~term) query parsing."""

    def setUp(self):
        self.parser = QueryParser()

    def test_semantic_prefix(self):
        result = self.parser.parse("~algorithm")
        self.assertEqual(result.query_type, QueryType.SEMANTIC)
        self.assertEqual(result.semantic_terms, ("algorithm",))

    def test_semantic_token_type(self):
        result = self.parser.parse("~foo")
        self.assertEqual(result.tokens[0].token_type, TokenType.SEMANTIC)
        self.assertEqual(result.tokens[0].value, "foo")

    def test_only_semantic_is_semantic_type(self):
        result = self.parser.parse("~algo ~sort")
        self.assertEqual(result.query_type, QueryType.SEMANTIC)

    def test_semantic_with_keyword_is_keyword_type(self):
        result = self.parser.parse("~algo keyword")
        self.assertEqual(result.query_type, QueryType.KEYWORD)
        self.assertEqual(result.semantic_terms, ("algo",))
        self.assertEqual(result.terms, ("keyword",))


class TestQueryParserExact(unittest.TestCase):
    """Test exact (=term) query parsing."""

    def setUp(self):
        self.parser = QueryParser()

    def test_exact_prefix(self):
        result = self.parser.parse("=myFunction")
        self.assertEqual(result.query_type, QueryType.EXACT)
        self.assertEqual(result.exact_terms, ("myFunction",))

    def test_exact_token_type(self):
        result = self.parser.parse("=foo")
        self.assertEqual(result.tokens[0].token_type, TokenType.EXACT)
        self.assertEqual(result.tokens[0].value, "foo")

    def test_only_exact_is_exact_type(self):
        result = self.parser.parse("=one =two")
        self.assertEqual(result.query_type, QueryType.EXACT)

    def test_exact_with_keyword_is_keyword_type(self):
        result = self.parser.parse("=exact keyword")
        self.assertEqual(result.query_type, QueryType.KEYWORD)


class TestQueryParserFilters(unittest.TestCase):
    """Test filter parsing: type, path, scope."""

    def setUp(self):
        self.parser = QueryParser()

    def test_type_filter(self):
        result = self.parser.parse("search type:py")
        self.assertEqual(result.type_filters, ("py",))
        self.assertEqual(result.terms, ("search",))

    def test_multiple_type_filters(self):
        result = self.parser.parse("search type:py type:js")
        self.assertEqual(result.type_filters, ("py", "js"))

    def test_path_filter(self):
        result = self.parser.parse("search path:src/")
        self.assertEqual(result.path_filters, ("src/",))

    def test_scope_filter(self):
        result = self.parser.parse("search scope:codebase")
        self.assertEqual(result.scope, "codebase")

    def test_scope_filter_token(self):
        result = self.parser.parse("scope:file")
        self.assertEqual(result.scope, "file")
        self.assertTrue(any(t.token_type == TokenType.FILTER_SCOPE for t in result.tokens))

    def test_type_filter_token(self):
        result = self.parser.parse("type:py")
        self.assertTrue(any(t.token_type == TokenType.FILTER_TYPE for t in result.tokens))
        self.assertTrue(any(t.value == "py" for t in result.tokens))

    def test_path_filter_token(self):
        result = self.parser.parse("path:tests/")
        self.assertTrue(any(t.token_type == TokenType.FILTER_PATH for t in result.tokens))


class TestQueryParserEdgeCases(unittest.TestCase):
    """Test edge cases."""

    def setUp(self):
        self.parser = QueryParser()

    def test_empty_query(self):
        result = self.parser.parse("")
        self.assertEqual(result.query_type, QueryType.KEYWORD)
        self.assertEqual(result.terms, ())
        self.assertEqual(result.tokens, ())
        self.assertEqual(result.raw_query, "")

    def test_only_filters(self):
        result = self.parser.parse("type:py path:src/")
        self.assertEqual(result.query_type, QueryType.KEYWORD)
        self.assertEqual(result.terms, ())
        self.assertEqual(result.type_filters, ("py",))
        self.assertEqual(result.path_filters, ("src/",))

    def test_mixed_types(self):
        result = self.parser.parse('~semantic "phrase" keyword =exact type:py')
        self.assertEqual(result.query_type, QueryType.KEYWORD)
        self.assertEqual(result.semantic_terms, ("semantic",))
        self.assertEqual(result.phrases, ("phrase",))
        self.assertEqual(result.terms, ("keyword",))
        self.assertEqual(result.exact_terms, ("exact",))
        self.assertEqual(result.type_filters, ("py",))

    def test_empty_quoted_phrase(self):
        result = self.parser.parse('""')
        self.assertEqual(result.phrases, ("",))

    def test_parsed_query_is_frozen(self):
        result = self.parser.parse("test")
        self.assertIsInstance(result, ParsedQuery)
        # Tuples are immutable so this should hold
        self.assertIsInstance(result.terms, tuple)
        self.assertIsInstance(result.tokens, tuple)

    def test_query_type_inference_keyword_default(self):
        result = self.parser.parse("just some keywords")
        self.assertEqual(result.query_type, QueryType.KEYWORD)

    def test_raw_query_preserved(self):
        raw = "  hello world  "
        result = self.parser.parse(raw)
        self.assertEqual(result.raw_query, raw)


class TestQueryToken(unittest.TestCase):
    """Test QueryToken dataclass."""

    def test_frozen(self):
        token = QueryToken(TokenType.TERM, "hello")
        with self.assertRaises(AttributeError):
            token.value = "world"

    def test_equality(self):
        a = QueryToken(TokenType.TERM, "hello")
        b = QueryToken(TokenType.TERM, "hello")
        self.assertEqual(a, b)


if __name__ == "__main__":
    unittest.main()
