import pytest

from vcm_file_uploader.state import JsonlStore, StateManager


class TestJsonlStore:
    def test_append_and_read(self, tmp_path):
        store = JsonlStore(tmp_path / "test.jsonl")
        store.append({"path": "a.txt", "size": 100})
        store.append({"path": "b.txt", "size": 200})

        entries = store.read_all()
        assert len(entries) == 2
        assert entries["a.txt"]["size"] == 100
        assert entries["b.txt"]["size"] == 200

    def test_last_write_wins(self, tmp_path):
        store = JsonlStore(tmp_path / "test.jsonl")
        store.append({"path": "a.txt", "size": 100})
        store.append({"path": "a.txt", "size": 999})

        entries = store.read_all()
        assert len(entries) == 1
        assert entries["a.txt"]["size"] == 999

    def test_get_existing(self, tmp_path):
        store = JsonlStore(tmp_path / "test.jsonl")
        store.append({"path": "a.txt", "size": 100})

        entry = store.get("a.txt")
        assert entry is not None
        assert entry["size"] == 100

    def test_get_missing(self, tmp_path):
        store = JsonlStore(tmp_path / "test.jsonl")
        assert store.get("missing") is None

    def test_read_empty_file(self, tmp_path):
        store = JsonlStore(tmp_path / "empty.jsonl")
        assert store.read_all() == {}

    def test_read_nonexistent_file(self, tmp_path):
        store = JsonlStore(tmp_path / "nope.jsonl")
        assert store.read_all() == {}

    def test_missing_key_field_raises(self, tmp_path):
        store = JsonlStore(tmp_path / "test.jsonl")
        with pytest.raises(ValueError, match="missing required key field"):
            store.append({"name": "a.txt"})

    def test_custom_key_field(self, tmp_path):
        store = JsonlStore(tmp_path / "test.jsonl", key_field="segment_key")
        store.append({"segment_key": "seg-001", "offset": 0})
        store.append({"segment_key": "seg-002", "offset": 1024})

        entries = store.read_all()
        assert len(entries) == 2
        assert entries["seg-001"]["offset"] == 0

    def test_skips_malformed_lines(self, tmp_path):
        path = tmp_path / "test.jsonl"
        path.write_text(
            '{"path": "good.txt", "size": 1}\n'
            "not valid json\n"
            '{"path": "also_good.txt", "size": 2}\n'
        )
        store = JsonlStore(path)

        entries = store.read_all()
        assert len(entries) == 2

    def test_skips_blank_lines(self, tmp_path):
        path = tmp_path / "test.jsonl"
        path.write_text(
            '{"path": "a.txt", "size": 1}\n'
            "\n"
            '{"path": "b.txt", "size": 2}\n'
        )
        store = JsonlStore(path)
        assert len(store.read_all()) == 2

    def test_creates_parent_dirs(self, tmp_path):
        store = JsonlStore(tmp_path / "deep" / "nested" / "state.jsonl")
        store.append({"path": "a.txt", "size": 1})

        assert store.get("a.txt") is not None


class TestJsonlCompaction:
    def test_compact_deduplicates(self, tmp_path):
        store = JsonlStore(tmp_path / "test.jsonl")
        store.append({"path": "a.txt", "size": 1})
        store.append({"path": "a.txt", "size": 2})
        store.append({"path": "b.txt", "size": 3})

        count = store.compact()
        assert count == 2

        # File should now have exactly 2 lines
        lines = store.path.read_text().strip().split("\n")
        assert len(lines) == 2

        # Values should be latest
        entries = store.read_all()
        assert entries["a.txt"]["size"] == 2
        assert entries["b.txt"]["size"] == 3

    def test_compact_empty_removes_file(self, tmp_path):
        path = tmp_path / "test.jsonl"
        path.write_text("")
        store = JsonlStore(path)

        count = store.compact()
        assert count == 0
        assert not path.exists()

    def test_compact_nonexistent(self, tmp_path):
        store = JsonlStore(tmp_path / "nope.jsonl")
        count = store.compact()
        assert count == 0

    def test_compact_preserves_data_integrity(self, tmp_path):
        store = JsonlStore(tmp_path / "test.jsonl")
        for i in range(100):
            store.append({"path": f"file_{i % 10}.txt", "version": i})

        count = store.compact()
        assert count == 10

        entries = store.read_all()
        for i in range(10):
            assert entries[f"file_{i}.txt"]["version"] == 90 + i


class TestStateManager:
    def test_creates_stores(self, tmp_path):
        mgr = StateManager(tmp_path / "state")
        assert mgr.files.key_field == "path"
        assert mgr.segments.key_field == "segment_key"
        assert mgr.runs.key_field == "run_id"

    def test_files_store(self, tmp_path):
        mgr = StateManager(tmp_path / "state")
        mgr.files.append({"path": "a.nc", "size": 1000, "sha256": "abc"})

        entry = mgr.files.get("a.nc")
        assert entry is not None
        assert entry["sha256"] == "abc"

    def test_segments_store(self, tmp_path):
        mgr = StateManager(tmp_path / "state")
        mgr.segments.append({"segment_key": "log.out:0-1024", "offset": 1024})

        entry = mgr.segments.get("log.out:0-1024")
        assert entry is not None
        assert entry["offset"] == 1024

    def test_runs_store(self, tmp_path):
        mgr = StateManager(tmp_path / "state")
        mgr.runs.append({"run_id": "run-001", "status": "completed"})

        entry = mgr.runs.get("run-001")
        assert entry is not None
        assert entry["status"] == "completed"

    def test_compact_all(self, tmp_path):
        mgr = StateManager(tmp_path / "state")
        mgr.files.append({"path": "a.nc", "v": 1})
        mgr.files.append({"path": "a.nc", "v": 2})
        mgr.segments.append({"segment_key": "s1", "v": 1})
        mgr.runs.append({"run_id": "r1", "v": 1})

        counts = mgr.compact_all()
        assert counts == {"files": 1, "segments": 1, "runs": 1}
