"""Tests for the RBSP integration module (filesystem and git)."""

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

import unittest

from rbsp.runtime.engine import RBSPEngine


# ---------------------------------------------------------------------------
# Filesystem Integration Tests
# ---------------------------------------------------------------------------

class TestRBSPFileSystem(RBSPTestCase):
    """Tests for rbsp.integration.filesystem.RBSPFileSystem."""

    def setUp(self):
        super().setUp()
        RBSPEngine.reset()

    def tearDown(self):
        RBSPEngine.reset()
        super().tearDown()

    def _make_fs(self):
        from rbsp.integration.filesystem import RBSPFileSystem
        return RBSPFileSystem(root=self.tmp_dir, auto_index=True)

    def test_write_and_read(self):
        fs = self._make_fs()
        ok = fs.write("hello.py", "print('hello world')\n")
        self.assertTrue(ok)
        content = fs.read("hello.py")
        self.assertEqual(content, "print('hello world')\n")

    def test_write_creates_subdirectories(self):
        fs = self._make_fs()
        ok = fs.write("sub/dir/file.txt", "nested content")
        self.assertTrue(ok)
        content = fs.read("sub/dir/file.txt")
        self.assertEqual(content, "nested content")

    def test_delete_file(self):
        fs = self._make_fs()
        fs.write("todelete.txt", "delete me")
        self.assertTrue(fs.exists("todelete.txt"))
        ok = fs.delete("todelete.txt")
        self.assertTrue(ok)
        self.assertFalse(fs.exists("todelete.txt"))

    def test_move_file(self):
        fs = self._make_fs()
        fs.write("original.txt", "move me")
        ok = fs.move("original.txt", "moved.txt")
        self.assertTrue(ok)
        self.assertFalse(fs.exists("original.txt"))
        self.assertTrue(fs.exists("moved.txt"))
        content = fs.read("moved.txt")
        self.assertEqual(content, "move me")

    def test_exists(self):
        fs = self._make_fs()
        self.assertFalse(fs.exists("nope.txt"))
        fs.write("yes.txt", "here")
        self.assertTrue(fs.exists("yes.txt"))

    def test_list_files(self):
        fs = self._make_fs()
        fs.write("a.py", "# a")
        fs.write("b.py", "# b")
        fs.write("c.txt", "text")
        files = fs.list_files()
        # Should find our files (plus possibly .rbsp config files)
        basenames = [os.path.basename(f) for f in files]
        self.assertIn("a.py", basenames)
        self.assertIn("b.py", basenames)

    def test_search_after_write(self):
        fs = self._make_fs()
        fs.write("auth.py", "def authenticate(user, password):\n    pass\n")
        fs.write("utils.py", "def format_date(date):\n    pass\n")
        # Force index all files
        fs.index_all(force=True)
        results = fs.search("authenticate")
        self.assertGreater(len(results), 0)

    def test_index_all(self):
        fs = self._make_fs()
        fs.write("code.py", SAMPLE_CODE)
        stats = fs.index_all(force=True)
        self.assertIn("files_indexed", stats)
        self.assertGreaterEqual(stats["files_indexed"], 1)
        self.assertIn("duration", stats)

    def test_history_tracking(self):
        fs = self._make_fs()
        fs.write("track.txt", "tracked write")
        fs.read("track.txt")
        fs.delete("track.txt")
        history = fs.history
        self.assertGreaterEqual(len(history), 3)
        ops = [h.operation for h in history]
        self.assertIn("write", ops)
        self.assertIn("read", ops)
        self.assertIn("delete", ops)

    def test_root_property(self):
        fs = self._make_fs()
        from pathlib import Path
        self.assertEqual(fs.root, Path(self.tmp_dir).resolve())

    def test_write_bytes(self):
        fs = self._make_fs()
        ok = fs.write_bytes("binary.dat", b"\x00\x01\x02\x03")
        self.assertTrue(ok)
        self.assertTrue(fs.exists("binary.dat"))

    def test_read_nonexistent_returns_empty(self):
        fs = self._make_fs()
        content = fs.read("doesnotexist.txt")
        self.assertEqual(content, "")


# ---------------------------------------------------------------------------
# Git Integration Tests
# ---------------------------------------------------------------------------

class TestGitIntegration(RBSPTestCase):
    """Tests for rbsp.integration.git module."""

    def setUp(self):
        super().setUp()
        RBSPEngine.reset()

    def tearDown(self):
        RBSPEngine.reset()
        super().tearDown()

    def test_gitignore_parse_basic(self):
        from rbsp.integration.git import parse_gitignore, is_ignored

        content = """\
# Comment
*.pyc
__pycache__/
.env
!important.pyc
"""
        rules = parse_gitignore(content)
        self.assertGreater(len(rules), 0)

        # *.pyc should be ignored
        self.assertTrue(is_ignored("foo.pyc", False, rules))
        # __pycache__ dir should be ignored
        self.assertTrue(is_ignored("__pycache__", True, rules))
        # .env should be ignored
        self.assertTrue(is_ignored(".env", False, rules))
        # important.pyc is negated -> NOT ignored
        self.assertFalse(is_ignored("important.pyc", False, rules))

    def test_gitignore_parse_nested_patterns(self):
        from rbsp.integration.git import parse_gitignore, is_ignored

        content = """\
build/
docs/*.html
**/node_modules/
"""
        rules = parse_gitignore(content)

        self.assertTrue(is_ignored("build", True, rules))
        self.assertTrue(is_ignored("docs/index.html", False, rules))
        self.assertTrue(is_ignored("src/node_modules", True, rules))

    def test_gitignore_empty_content(self):
        from rbsp.integration.git import parse_gitignore
        rules = parse_gitignore("")
        self.assertEqual(len(rules), 0)

    def test_gitignore_comments_only(self):
        from rbsp.integration.git import parse_gitignore
        rules = parse_gitignore("# just a comment\n# another\n")
        self.assertEqual(len(rules), 0)

    def test_is_ignored_normalizes_slashes(self):
        from rbsp.integration.git import parse_gitignore, is_ignored

        rules = parse_gitignore("build/\n")
        # Windows-style backslash should still work
        self.assertTrue(is_ignored("build", True, rules))

    def test_git_integration_class_instantiation(self):
        from rbsp.integration.git import GitIntegration
        git = GitIntegration()
        self.assertIsNotNone(git)

    def test_git_integration_index_repo(self):
        from rbsp.integration.git import GitIntegration

        # Write some files to the temp dir
        self.make_sample_file("main.py", "def main():\n    print('hello')\n")
        self.make_sample_file("lib.py", "class Library:\n    pass\n")

        git = GitIntegration()
        stats = git.index_repo(self.tmp_dir)
        self.assertIn("files_indexed", stats)
        self.assertIn("method", stats)
        self.assertIn("duration", stats)
        # Method should be either "git" or "walk"
        self.assertIn(stats["method"], ("git", "walk"))

    def test_git_integration_search_repo(self):
        from rbsp.integration.git import GitIntegration

        self.make_sample_file("auth.py", "def authenticate(user, password):\n    pass\n")
        self.make_sample_file("util.py", "def format_date(d):\n    return str(d)\n")

        git = GitIntegration()
        git.index_repo(self.tmp_dir)
        results = git.search_repo(self.tmp_dir, "authenticate")
        # Should return results
        self.assertIsInstance(results, list)

    def test_git_integration_get_tracked_files(self):
        from rbsp.integration.git import GitIntegration

        self.make_sample_file("file1.py", "# file 1")
        self.make_sample_file("file2.txt", "file 2 content")

        git = GitIntegration()
        files = git.get_tracked_files(self.tmp_dir)
        self.assertIsInstance(files, list)
        # Should find at least some files
        self.assertGreater(len(files), 0)

    def test_git_integration_indexed_repos_property(self):
        from rbsp.integration.git import GitIntegration
        git = GitIntegration()
        self.assertEqual(git.indexed_repos, [])

        self.make_sample_file("test.py", "pass")
        git.index_repo(self.tmp_dir)
        self.assertGreater(len(git.indexed_repos), 0)

    def test_git_integration_is_git_repo(self):
        from rbsp.integration.git import GitIntegration
        git = GitIntegration()
        # Temp dir is typically NOT a git repo
        result = git.is_git_repo(self.tmp_dir)
        self.assertIsInstance(result, bool)

    def test_git_integration_repo_info(self):
        from rbsp.integration.git import GitIntegration
        git = GitIntegration()
        info = git.repo_info(self.tmp_dir)
        self.assertIsInstance(info, dict)
        self.assertIn("path", info)
        self.assertIn("is_git", info)

    def test_manual_walk_respects_gitignore(self):
        from rbsp.integration.git import parse_gitignore, _manual_walk

        # Create files and a .gitignore in temp dir
        self.make_sample_file("keep.py", "# keep")
        self.make_sample_file("skip.pyc", "# compiled")

        # Create a .gitignore that excludes *.pyc
        gitignore_path = os.path.join(self.tmp_dir, ".gitignore")
        with open(gitignore_path, "w") as f:
            f.write("*.pyc\n")

        rules = parse_gitignore("*.pyc\n")
        files = _manual_walk(self.tmp_dir, rules)

        file_basenames = [os.path.basename(f) for f in files]
        self.assertIn("keep.py", file_basenames)
        self.assertNotIn("skip.pyc", file_basenames)


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