"""Tests for the chunking module."""

from __future__ import annotations

from pathlib import Path

from redhat_oracle.core.vectordb.chunker import (
    _generate_chunk_id,
    _is_vendored,
    chunk_file,
    chunk_fixed_size,
    chunk_python_ast,
    chunk_repo,
)


class TestChunkIdGeneration:
    def test_deterministic_ids(self) -> None:
        id1 = _generate_chunk_id("github", "org", "repo", "file.py", "content")
        id2 = _generate_chunk_id("github", "org", "repo", "file.py", "content")
        assert id1 == id2

    def test_different_content_different_id(self) -> None:
        id1 = _generate_chunk_id("github", "org", "repo", "file.py", "content A")
        id2 = _generate_chunk_id("github", "org", "repo", "file.py", "content B")
        assert id1 != id2

    def test_id_format(self) -> None:
        chunk_id = _generate_chunk_id("github", "myorg", "myrepo", "src/main.py", "hello")
        assert chunk_id.startswith("github/myorg/myrepo/src/main.py::")
        # sha256[:12] = 12 hex chars
        hash_part = chunk_id.split("::")[-1]
        assert len(hash_part) == 12


class TestIsVendored:
    def test_normal_path(self) -> None:
        assert _is_vendored(Path("src/main.py")) is False

    def test_node_modules(self) -> None:
        assert _is_vendored(Path("frontend/node_modules/lodash/index.js")) is True

    def test_git_dir(self) -> None:
        assert _is_vendored(Path(".git/objects/abc123")) is True

    def test_pycache(self) -> None:
        assert _is_vendored(Path("src/__pycache__/module.cpython-311.pyc")) is True

    def test_vendor_dir(self) -> None:
        assert _is_vendored(Path("vendor/lib/package.py")) is True


class TestPythonAstChunking:
    def test_functions_become_chunks(self) -> None:
        source = '''
def hello():
    """Say hello."""
    print("hello")

def goodbye():
    """Say goodbye."""
    print("goodbye")
'''
        chunks = chunk_python_ast(source, "test.py", "github", "org", "repo")
        assert chunks is not None
        assert len(chunks) == 2
        assert chunks[0].node_type == "function"
        assert chunks[0].node_name == "hello"
        assert chunks[1].node_name == "goodbye"

    def test_classes_become_chunks(self) -> None:
        source = '''
class MyClass:
    """A test class."""
    def method(self):
        pass
'''
        chunks = chunk_python_ast(source, "test.py", "github", "org", "repo")
        assert chunks is not None
        # Should include the class (and method is inside class, walked too)
        names = {c.node_name for c in chunks}
        assert "MyClass" in names

    def test_syntax_error_returns_none(self) -> None:
        source = "def broken(:\n    pass"
        result = chunk_python_ast(source, "test.py", "github", "org", "repo")
        assert result is None

    def test_empty_file_returns_none(self) -> None:
        result = chunk_python_ast("", "test.py", "github", "org", "repo")
        assert result is None

    def test_chunk_ids_are_deterministic(self) -> None:
        source = '''
def hello():
    print("hello")
'''
        chunks1 = chunk_python_ast(source, "test.py", "github", "org", "repo")
        chunks2 = chunk_python_ast(source, "test.py", "github", "org", "repo")
        assert chunks1 is not None and chunks2 is not None
        assert chunks1[0].chunk_id == chunks2[0].chunk_id


class TestFixedSizeChunking:
    def test_basic_chunking(self) -> None:
        words = " ".join(f"word{i}" for i in range(100))
        chunks = chunk_fixed_size(words, "file.txt", "github", "org", "repo", chunk_size=50)
        assert len(chunks) >= 2

    def test_overlap(self) -> None:
        words = " ".join(f"word{i}" for i in range(100))
        chunks = chunk_fixed_size(
            words, "file.txt", "github", "org", "repo",
            chunk_size=50, chunk_overlap=10,
        )
        # With overlap, chunks should share some words
        if len(chunks) >= 2:
            words_1 = set(chunks[0].text.split())
            words_2 = set(chunks[1].text.split())
            assert words_1 & words_2  # Some overlap

    def test_empty_text(self) -> None:
        chunks = chunk_fixed_size("", "file.txt", "github", "org", "repo")
        assert chunks == []

    def test_small_text_single_chunk(self) -> None:
        chunks = chunk_fixed_size(
            "a few words here", "file.txt", "github", "org", "repo",
            chunk_size=500,
        )
        assert len(chunks) == 1


class TestChunkFile:
    def test_python_file_uses_ast(self, tmp_path: Path) -> None:
        py_file = tmp_path / "test.py"
        py_file.write_text('def foo():\n    return 42\n\ndef bar():\n    return 99\n')

        chunks = chunk_file(py_file, tmp_path, "github", "org", "repo")
        assert len(chunks) == 2
        assert all(c.node_type == "function" for c in chunks)

    def test_python_syntax_error_falls_back_to_fixed(self, tmp_path: Path) -> None:
        py_file = tmp_path / "bad.py"
        py_file.write_text("def broken(:\n" + " ".join(f"word{i}" for i in range(100)))

        chunks = chunk_file(py_file, tmp_path, "github", "org", "repo")
        # Should fall back to fixed chunking
        assert len(chunks) >= 1
        assert all(c.node_type is None for c in chunks)

    def test_non_python_uses_fixed(self, tmp_path: Path) -> None:
        js_file = tmp_path / "index.js"
        js_file.write_text("const x = 1;\n" * 100)

        chunks = chunk_file(js_file, tmp_path, "github", "org", "repo")
        assert len(chunks) >= 1
        assert all(c.node_type is None for c in chunks)

    def test_skips_large_files(self, tmp_path: Path) -> None:
        big_file = tmp_path / "big.py"
        big_file.write_text("x" * (512 * 1024 + 1))

        chunks = chunk_file(big_file, tmp_path, "github", "org", "repo")
        assert chunks == []

    def test_skips_unknown_extensions(self, tmp_path: Path) -> None:
        bin_file = tmp_path / "data.bin"
        bin_file.write_text("binary stuff")

        chunks = chunk_file(bin_file, tmp_path, "github", "org", "repo")
        assert chunks == []

    def test_skips_vendored_dirs(self, tmp_path: Path) -> None:
        vendor_dir = tmp_path / "node_modules" / "lodash"
        vendor_dir.mkdir(parents=True)
        js_file = vendor_dir / "index.js"
        js_file.write_text("module.exports = {};")

        chunks = chunk_file(js_file, tmp_path, "github", "org", "repo")
        assert chunks == []


class TestChunkRepo:
    def test_chunks_entire_repo(self, tmp_path: Path) -> None:
        # Create some files
        (tmp_path / "main.py").write_text("def main():\n    pass\n")
        (tmp_path / "utils.py").write_text("def helper():\n    pass\n")
        sub = tmp_path / "sub"
        sub.mkdir()
        (sub / "module.py").write_text("class Foo:\n    pass\n")

        chunks = chunk_repo(tmp_path, "github", "org", "repo")
        assert len(chunks) >= 3  # At least one per function/class

    def test_skips_vendored(self, tmp_path: Path) -> None:
        (tmp_path / "main.py").write_text("def main():\n    pass\n")
        vendor = tmp_path / "node_modules" / "pkg"
        vendor.mkdir(parents=True)
        (vendor / "index.js").write_text("module.exports = {};")

        chunks = chunk_repo(tmp_path, "github", "org", "repo")
        paths = {c.path for c in chunks}
        assert not any("node_modules" in p for p in paths)
