"""Tests for rbsp.integration.git -- gitignore parser and matching utilities."""

import os
import sys
import unittest

sys.path.insert(0, os.path.dirname(__file__))
from conftest import RBSPTestCase

from rbsp.integration.git import (
    parse_gitignore,
    is_ignored,
    _gitignore_pattern_to_regex,
    _GitignoreRule,
)


# ---------------------------------------------------------------------------
# _gitignore_pattern_to_regex
# ---------------------------------------------------------------------------

class TestGitignorePatternToRegex(unittest.TestCase):
    """Low-level pattern-to-regex conversion."""

    def test_literal_text(self):
        regex = _gitignore_pattern_to_regex("foo")
        self.assertEqual(regex, "foo")

    def test_single_star(self):
        regex = _gitignore_pattern_to_regex("*.py")
        self.assertIn("[^/]*", regex)

    def test_double_star_standalone(self):
        regex = _gitignore_pattern_to_regex("**")
        self.assertIn(".*", regex)

    def test_double_star_slash(self):
        regex = _gitignore_pattern_to_regex("**/foo")
        self.assertIn("(?:.+/)?", regex)

    def test_question_mark(self):
        regex = _gitignore_pattern_to_regex("f?o")
        self.assertIn("[^/]", regex)

    def test_character_class(self):
        regex = _gitignore_pattern_to_regex("[abc]")
        self.assertIn("[abc]", regex)

    def test_character_class_negation(self):
        regex = _gitignore_pattern_to_regex("[!abc]")
        self.assertIn("[^abc]", regex)

    def test_escaped_character(self):
        regex = _gitignore_pattern_to_regex("\\*")
        self.assertIn("\\*", regex)

    def test_dot_escaped(self):
        regex = _gitignore_pattern_to_regex("foo.py")
        self.assertIn("\\.", regex)


# ---------------------------------------------------------------------------
# parse_gitignore -- comments, blank lines
# ---------------------------------------------------------------------------

class TestParseGitignoreComments(unittest.TestCase):
    """Comments and blank lines are skipped."""

    def test_comments_ignored(self):
        rules = parse_gitignore("# this is a comment\n")
        self.assertEqual(len(rules), 0)

    def test_blank_lines_ignored(self):
        rules = parse_gitignore("\n\n\n")
        self.assertEqual(len(rules), 0)

    def test_mixed_comments_and_patterns(self):
        content = "# comment\n*.pyc\n\n# another\nbuild/\n"
        rules = parse_gitignore(content)
        self.assertEqual(len(rules), 2)

    def test_inline_content_not_comment(self):
        # A line starting with # is a comment; pattern is not
        rules = parse_gitignore("foo\n#bar\nbaz\n")
        self.assertEqual(len(rules), 2)


# ---------------------------------------------------------------------------
# parse_gitignore -- negation
# ---------------------------------------------------------------------------

class TestParseGitignoreNegation(unittest.TestCase):
    """Negation with ! prefix."""

    def test_negation_flag_set(self):
        rules = parse_gitignore("!important.txt\n")
        self.assertEqual(len(rules), 1)
        self.assertTrue(rules[0].negated)

    def test_non_negated_flag_unset(self):
        rules = parse_gitignore("*.log\n")
        self.assertFalse(rules[0].negated)

    def test_negation_overrides_ignore(self):
        content = "*.log\n!important.log\n"
        rules = parse_gitignore(content)
        # important.log should NOT be ignored (negation wins, last match)
        self.assertFalse(is_ignored("important.log", False, rules))
        # other.log should be ignored
        self.assertTrue(is_ignored("other.log", False, rules))


# ---------------------------------------------------------------------------
# parse_gitignore -- dir_only
# ---------------------------------------------------------------------------

class TestParseGitignoreDirOnly(unittest.TestCase):
    """Trailing / makes pattern directory-only."""

    def test_trailing_slash_sets_dir_only(self):
        rules = parse_gitignore("build/\n")
        self.assertEqual(len(rules), 1)
        self.assertTrue(rules[0].dir_only)

    def test_no_trailing_slash_not_dir_only(self):
        rules = parse_gitignore("build\n")
        self.assertFalse(rules[0].dir_only)

    def test_dir_only_ignores_directory(self):
        rules = parse_gitignore("build/\n")
        self.assertTrue(is_ignored("build", True, rules))

    def test_dir_only_does_not_ignore_file(self):
        rules = parse_gitignore("build/\n")
        self.assertFalse(is_ignored("build", False, rules))


# ---------------------------------------------------------------------------
# parse_gitignore -- glob patterns
# ---------------------------------------------------------------------------

class TestParseGitignoreGlobs(unittest.TestCase):
    """Glob pattern support."""

    def test_star_matches_extension(self):
        rules = parse_gitignore("*.pyc\n")
        self.assertTrue(is_ignored("module.pyc", False, rules))
        self.assertFalse(is_ignored("module.py", False, rules))

    def test_double_star_matches_deep_path(self):
        rules = parse_gitignore("**/test\n")
        self.assertTrue(is_ignored("a/b/c/test", False, rules))
        self.assertTrue(is_ignored("test", False, rules))

    def test_question_mark(self):
        rules = parse_gitignore("file?.txt\n")
        self.assertTrue(is_ignored("fileA.txt", False, rules))
        self.assertFalse(is_ignored("file.txt", False, rules))

    def test_star_does_not_match_slash(self):
        rules = parse_gitignore("*.py\n")
        # *.py should match "foo.py" but the star does not cross /
        self.assertTrue(is_ignored("foo.py", False, rules))
        # deep/foo.py -- basename matching allows this because the rule is unanchored
        self.assertTrue(is_ignored("deep/foo.py", False, rules))

    def test_anchored_pattern_with_slash(self):
        rules = parse_gitignore("src/*.py\n")
        self.assertTrue(is_ignored("src/main.py", False, rules))
        self.assertFalse(is_ignored("lib/main.py", False, rules))

    def test_rooted_pattern(self):
        rules = parse_gitignore("/root_only.txt\n")
        self.assertTrue(is_ignored("root_only.txt", False, rules))
        # Should not match deeper paths
        self.assertFalse(is_ignored("sub/root_only.txt", False, rules))


# ---------------------------------------------------------------------------
# is_ignored
# ---------------------------------------------------------------------------

class TestIsIgnored(unittest.TestCase):
    """is_ignored with various rule sets."""

    def test_empty_rules_nothing_ignored(self):
        self.assertFalse(is_ignored("anything.txt", False, []))

    def test_backslash_normalized(self):
        rules = parse_gitignore("*.log\n")
        # Windows-style path should be normalized
        self.assertTrue(is_ignored("logs\\debug.log", False, rules))

    def test_leading_slash_stripped(self):
        rules = parse_gitignore("*.txt\n")
        self.assertTrue(is_ignored("/readme.txt", False, rules))

    def test_last_matching_rule_wins(self):
        content = "*.txt\n!keep.txt\n"
        rules = parse_gitignore(content)
        self.assertTrue(is_ignored("drop.txt", False, rules))
        self.assertFalse(is_ignored("keep.txt", False, rules))

    def test_multiple_rules(self):
        content = "*.pyc\n__pycache__/\n*.egg-info/\n"
        rules = parse_gitignore(content)
        self.assertTrue(is_ignored("module.pyc", False, rules))
        self.assertTrue(is_ignored("__pycache__", True, rules))
        self.assertTrue(is_ignored("foo.egg-info", True, rules))
        self.assertFalse(is_ignored("module.py", False, rules))

    def test_node_modules(self):
        rules = parse_gitignore("node_modules/\n")
        self.assertTrue(is_ignored("node_modules", True, rules))
        self.assertFalse(is_ignored("node_modules", False, rules))

    def test_complex_gitignore(self):
        content = """\
# Python
*.pyc
__pycache__/
*.egg-info/

# IDE
.idea/
.vscode/

# Build
build/
dist/

# Keep
!dist/important.py
"""
        rules = parse_gitignore(content)
        self.assertTrue(is_ignored("foo.pyc", False, rules))
        self.assertTrue(is_ignored("__pycache__", True, rules))
        self.assertTrue(is_ignored(".idea", True, rules))
        self.assertTrue(is_ignored("build", True, rules))
        self.assertTrue(is_ignored("dist", True, rules))


# ---------------------------------------------------------------------------
# _GitignoreRule
# ---------------------------------------------------------------------------

class TestGitignoreRule(unittest.TestCase):
    """_GitignoreRule repr and matches."""

    def test_repr_negated(self):
        rules = parse_gitignore("!keep.txt\n")
        r = repr(rules[0])
        self.assertIn("!", r)
        self.assertIn("keep.txt", r)

    def test_repr_normal(self):
        rules = parse_gitignore("*.log\n")
        r = repr(rules[0])
        self.assertIn("*.log", r)
        self.assertNotIn("!", r)


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