"""Tests for the delta context buffer."""

import pytest

from proxym.cache import DeltaBuffer, DeltaPayload, FileSnapshot


class TestFileSnapshot:
    def test_from_path(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("print('hello')")
        snap = FileSnapshot.from_path(f)
        assert snap is not None
        assert snap.content == "print('hello')"
        assert snap.size == 14
        assert snap.hash  # non-empty

    def test_from_path_missing(self, tmp_path):
        f = tmp_path / "nonexistent.py"
        snap = FileSnapshot.from_path(f)
        assert snap is None

    def test_hash_changes_with_content(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("version 1")
        h1 = FileSnapshot.from_path(f).hash
        f.write_text("version 2")
        h2 = FileSnapshot.from_path(f).hash
        assert h1 != h2

    def test_hash_stable_for_same_content(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("stable content")
        h1 = FileSnapshot.from_path(f).hash
        h2 = FileSnapshot.from_path(f).hash
        assert h1 == h2


class TestDeltaBuffer:
    def test_initial_scan(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project))
        delta = buf.compute_delta()
        assert delta.is_full_refresh is True
        assert delta.files_total >= 3  # main.py, utils.py, README.md, pyproject.toml
        assert delta.version == 1

    def test_no_changes_returns_full(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project))
        buf.compute_delta()  # initial
        delta = buf.compute_delta()  # no changes
        # With 0 changes, it should send full refresh
        assert delta.is_full_refresh is True
        assert delta.files_changed == 0

    def test_small_change_returns_diff(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project), delta_threshold=0.5)
        buf.compute_delta()  # initial

        # Modify one file
        (tmp_project / "src" / "main.py").write_text(
            "def main():\n    print('hello world')\n    return 0\n"
        )

        delta = buf.compute_delta()
        assert delta.files_changed == 1
        assert delta.is_full_refresh is False
        assert "main.py" in delta.diff_text
        assert "+    print('hello world')" in delta.diff_text or "hello world" in delta.diff_text

    def test_new_file_in_diff(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project), delta_threshold=0.5)
        buf.compute_delta()  # initial

        # Add new file
        (tmp_project / "src" / "new_module.py").write_text(
            "class NewModule:\n    pass\n"
        )

        delta = buf.compute_delta()
        assert delta.files_added == 1
        assert "new_module.py" in delta.diff_text

    def test_removed_file_in_diff(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project), delta_threshold=0.8)
        buf.compute_delta()  # initial

        # Remove file
        (tmp_project / "src" / "utils.py").unlink()

        delta = buf.compute_delta()
        assert delta.files_removed == 1
        assert "utils.py" in delta.diff_text

    def test_large_change_triggers_full_refresh(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project), delta_threshold=0.1)
        buf.compute_delta()  # initial

        # Change ALL files (exceeds threshold)
        (tmp_project / "src" / "main.py").write_text("# changed\n")
        (tmp_project / "src" / "utils.py").write_text("# changed\n")
        (tmp_project / "README.md").write_text("# changed\n")
        (tmp_project / "pyproject.toml").write_text("# changed\n")

        delta = buf.compute_delta()
        assert delta.is_full_refresh is True

    def test_version_increments(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project))
        d1 = buf.compute_delta()
        d2 = buf.compute_delta()
        d3 = buf.compute_delta()
        assert d1.version == 1
        assert d2.version == 2
        assert d3.version == 3

    def test_ignores_hidden_dirs(self, tmp_project):
        (tmp_project / ".git").mkdir()
        (tmp_project / ".git" / "config").write_text("secret")
        (tmp_project / "__pycache__").mkdir()
        (tmp_project / "__pycache__" / "main.cpython-312.pyc").write_text("bytecode")

        buf = DeltaBuffer(watch_dir=str(tmp_project))
        snapshots = buf.scan()
        for path in snapshots:
            assert ".git" not in path
            assert "__pycache__" not in path

    def test_respects_extensions_filter(self, tmp_project):
        (tmp_project / "image.png").write_bytes(b"\x89PNG\r\n")
        (tmp_project / "binary.exe").write_bytes(b"\x00\x01")

        buf = DeltaBuffer(watch_dir=str(tmp_project))
        snapshots = buf.scan()
        for path in snapshots:
            assert not path.endswith(".png")
            assert not path.endswith(".exe")

    def test_payload_format_full(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project))
        delta = buf.compute_delta()
        payload = delta.payload
        assert payload.startswith('<context version="1"')
        assert 'type="full"' in payload

    def test_payload_format_delta(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project), delta_threshold=0.5)
        buf.compute_delta()  # initial

        (tmp_project / "src" / "main.py").write_text("# v2\n")
        delta = buf.compute_delta()

        if not delta.is_full_refresh:
            payload = delta.payload
            assert payload.startswith('<context_delta version="2"')
            assert "files_changed" in payload

    def test_stats(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project))
        buf.compute_delta()
        stats = buf.get_stats()
        assert stats["version"] == 1
        assert stats["files_tracked"] >= 3
        assert stats["initialized"] is True

    def test_empty_dir(self, tmp_path):
        empty = tmp_path / "empty"
        empty.mkdir()
        buf = DeltaBuffer(watch_dir=str(empty))
        delta = buf.compute_delta()
        assert delta.files_total == 0
        assert delta.is_full_refresh is True

    def test_token_estimation(self, tmp_project):
        buf = DeltaBuffer(watch_dir=str(tmp_project))
        delta = buf.compute_delta()
        assert delta.token_estimate > 0
        # Rough sanity: at least a few tokens per file
        assert delta.token_estimate > delta.files_total
