"""Tests for glreview.registry module."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from glreview.registry import (
    SCHEMA_VERSION,
    ModuleStatus,
    Registry,
    load_registry,
    save_registry,
)


class TestModuleStatus:
    """Tests for ModuleStatus dataclass."""

    def test_default_values(self):
        """Default values are correct."""
        status = ModuleStatus(path="src/main.py")
        assert status.path == "src/main.py"
        assert status.status == "needs_review"
        assert status.priority == "medium"
        assert status.lines == 0
        assert status.reviewers_required == 1
        assert status.last_reviewed_commit is None
        assert status.last_reviewed_date is None
        assert status.reviewers == []
        assert status.review_issue is None
        assert status.assigned_reviewer is None
        assert status.review_started_commit is None
        assert status.notes == ""

    def test_custom_values(self):
        """Custom values are stored."""
        status = ModuleStatus(
            path="src/core.py",
            status="reviewed",
            priority="critical",
            lines=500,
            reviewers_required=2,
            last_reviewed_commit="abc123",
            last_reviewed_date="2024-01-15",
            reviewers=["@alice", "@bob"],
            review_issue="#42",
            assigned_reviewer="@alice",
            review_started_commit="def456",
            notes="Important module",
        )
        assert status.path == "src/core.py"
        assert status.status == "reviewed"
        assert status.priority == "critical"
        assert status.lines == 500
        assert status.reviewers == ["@alice", "@bob"]
        assert status.notes == "Important module"

    def test_to_dict(self):
        """to_dict excludes path and includes all other fields."""
        status = ModuleStatus(
            path="src/main.py",
            status="in_progress",
            priority="high",
            lines=100,
        )
        d = status.to_dict()
        assert "path" not in d
        assert d["status"] == "in_progress"
        assert d["priority"] == "high"
        assert d["lines"] == 100

    def test_from_dict(self):
        """from_dict creates ModuleStatus correctly."""
        data = {
            "status": "reviewed",
            "priority": "low",
            "lines": 50,
            "reviewers": ["@reviewer"],
            "last_reviewed_commit": "xyz789",
        }
        status = ModuleStatus.from_dict("lib/utils.py", data)
        assert status.path == "lib/utils.py"
        assert status.status == "reviewed"
        assert status.priority == "low"
        assert status.lines == 50
        assert status.reviewers == ["@reviewer"]
        assert status.last_reviewed_commit == "xyz789"

    def test_from_dict_defaults(self):
        """from_dict uses defaults for missing fields."""
        status = ModuleStatus.from_dict("src/module.py", {})
        assert status.path == "src/module.py"
        assert status.status == "needs_review"
        assert status.priority == "medium"
        assert status.lines == 0
        assert status.reviewers == []

    def test_roundtrip(self):
        """to_dict and from_dict are consistent."""
        original = ModuleStatus(
            path="src/main.py",
            status="in_progress",
            priority="high",
            lines=200,
            reviewers=["@alice"],
            review_issue="#10",
        )
        d = original.to_dict()
        restored = ModuleStatus.from_dict("src/main.py", d)
        assert restored.path == original.path
        assert restored.status == original.status
        assert restored.priority == original.priority
        assert restored.lines == original.lines
        assert restored.reviewers == original.reviewers


class TestRegistry:
    """Tests for Registry dataclass."""

    def test_empty_registry(self):
        """Empty registry is valid."""
        reg = Registry()
        assert len(reg) == 0
        assert reg.last_updated is None
        assert reg.current_commit is None

    def test_get_existing(self):
        """get returns existing module."""
        status = ModuleStatus(path="src/main.py", status="reviewed")
        reg = Registry(modules={"src/main.py": status})
        assert reg.get("src/main.py") == status

    def test_get_missing(self):
        """get returns None for missing module."""
        reg = Registry()
        assert reg.get("nonexistent.py") is None

    def test_set(self):
        """set adds or updates a module."""
        reg = Registry()
        status = ModuleStatus(path="src/main.py")
        reg.set(status)
        assert reg.get("src/main.py") == status

        # Update
        updated = ModuleStatus(path="src/main.py", status="reviewed")
        reg.set(updated)
        assert reg.get("src/main.py").status == "reviewed"

    def test_remove_existing(self):
        """remove returns True and removes existing module."""
        status = ModuleStatus(path="src/main.py")
        reg = Registry(modules={"src/main.py": status})
        assert reg.remove("src/main.py") is True
        assert reg.get("src/main.py") is None
        assert len(reg) == 0

    def test_remove_missing(self):
        """remove returns False for missing module."""
        reg = Registry()
        assert reg.remove("nonexistent.py") is False

    def test_iter(self):
        """Iteration yields all modules."""
        mod1 = ModuleStatus(path="a.py")
        mod2 = ModuleStatus(path="b.py")
        reg = Registry(modules={"a.py": mod1, "b.py": mod2})
        modules = list(reg)
        assert len(modules) == 2
        assert mod1 in modules
        assert mod2 in modules

    def test_len(self):
        """len returns module count."""
        reg = Registry()
        assert len(reg) == 0

        reg.set(ModuleStatus(path="a.py"))
        assert len(reg) == 1

        reg.set(ModuleStatus(path="b.py"))
        assert len(reg) == 2

    def test_filter_by_status(self):
        """filter by status works."""
        reg = Registry(
            modules={
                "a.py": ModuleStatus(path="a.py", status="reviewed"),
                "b.py": ModuleStatus(path="b.py", status="needs_review"),
                "c.py": ModuleStatus(path="c.py", status="reviewed"),
            }
        )
        reviewed = list(reg.filter(status="reviewed"))
        assert len(reviewed) == 2
        assert all(m.status == "reviewed" for m in reviewed)

    def test_filter_by_priority(self):
        """filter by priority works."""
        reg = Registry(
            modules={
                "a.py": ModuleStatus(path="a.py", priority="high"),
                "b.py": ModuleStatus(path="b.py", priority="low"),
                "c.py": ModuleStatus(path="c.py", priority="high"),
            }
        )
        high = list(reg.filter(priority="high"))
        assert len(high) == 2
        assert all(m.priority == "high" for m in high)

    def test_filter_by_both(self):
        """filter by status and priority works."""
        reg = Registry(
            modules={
                "a.py": ModuleStatus(path="a.py", status="reviewed", priority="high"),
                "b.py": ModuleStatus(
                    path="b.py", status="needs_review", priority="high"
                ),
                "c.py": ModuleStatus(path="c.py", status="reviewed", priority="low"),
            }
        )
        matches = list(reg.filter(status="reviewed", priority="high"))
        assert len(matches) == 1
        assert matches[0].path == "a.py"

    def test_summary(self):
        """summary returns correct counts."""
        reg = Registry(
            modules={
                "a.py": ModuleStatus(path="a.py", status="reviewed", lines=100),
                "b.py": ModuleStatus(path="b.py", status="reviewed", lines=50),
                "c.py": ModuleStatus(path="c.py", status="needs_review", lines=200),
                "d.py": ModuleStatus(path="d.py", status="in_progress", lines=75),
                "e.py": ModuleStatus(path="e.py", status="needs_update", lines=25),
            }
        )
        summary = reg.summary
        assert summary["total"] == 5
        assert summary["reviewed"] == 2
        assert summary["needs_review"] == 1
        assert summary["in_progress"] == 1
        assert summary["needs_update"] == 1
        assert summary["total_lines"] == 450
        assert summary["reviewed_lines"] == 150

    def test_to_dict(self):
        """to_dict returns correct structure."""
        status = ModuleStatus(path="src/main.py", status="reviewed")
        reg = Registry(
            modules={"src/main.py": status},
            last_updated="2024-01-15T12:00:00Z",
            current_commit="abc123",
        )
        d = reg.to_dict()
        assert d["version"] == SCHEMA_VERSION
        assert d["last_updated"] == "2024-01-15T12:00:00Z"
        assert d["current_commit"] == "abc123"
        assert "src/main.py" in d["modules"]
        assert d["modules"]["src/main.py"]["status"] == "reviewed"

    def test_from_dict(self):
        """from_dict creates Registry correctly."""
        data = {
            "version": "1",
            "last_updated": "2024-01-15T12:00:00Z",
            "current_commit": "abc123",
            "modules": {
                "src/main.py": {"status": "reviewed", "lines": 100},
                "src/utils.py": {"status": "needs_review"},
            },
        }
        reg = Registry.from_dict(data)
        assert reg.last_updated == "2024-01-15T12:00:00Z"
        assert reg.current_commit == "abc123"
        assert len(reg) == 2
        assert reg.get("src/main.py").status == "reviewed"
        assert reg.get("src/main.py").lines == 100

    def test_from_dict_empty(self):
        """from_dict handles empty data."""
        reg = Registry.from_dict({})
        assert len(reg) == 0
        assert reg.last_updated is None

    def test_roundtrip(self):
        """to_dict and from_dict are consistent."""
        original = Registry(
            modules={
                "a.py": ModuleStatus(path="a.py", status="reviewed", lines=100),
                "b.py": ModuleStatus(path="b.py", status="in_progress", reviewers=["@x"]),
            },
            last_updated="2024-01-15",
            current_commit="abc123",
        )
        d = original.to_dict()
        restored = Registry.from_dict(d)
        assert len(restored) == len(original)
        assert restored.last_updated == original.last_updated
        assert restored.current_commit == original.current_commit
        assert restored.get("a.py").status == "reviewed"
        assert restored.get("b.py").reviewers == ["@x"]


class TestLoadRegistry:
    """Tests for load_registry function."""

    def test_load_nonexistent_file(self, temp_dir):
        """Returns empty registry for nonexistent file."""
        reg = load_registry(temp_dir / "nonexistent.json")
        assert len(reg) == 0
        assert reg.last_updated is None

    def test_load_existing_file(self, temp_dir):
        """Loads registry from existing file."""
        data = {
            "version": "1",
            "last_updated": "2024-01-15",
            "current_commit": "abc123",
            "modules": {
                "src/main.py": {"status": "reviewed", "lines": 100},
            },
        }
        path = temp_dir / "registry.json"
        path.write_text(json.dumps(data))

        reg = load_registry(path)
        assert len(reg) == 1
        assert reg.get("src/main.py").status == "reviewed"
        assert reg.current_commit == "abc123"


class TestSaveRegistry:
    """Tests for save_registry function."""

    def test_save_creates_file(self, temp_dir):
        """save_registry creates file."""
        reg = Registry()
        reg.set(ModuleStatus(path="src/main.py", status="reviewed"))
        path = temp_dir / "registry.json"

        save_registry(reg, path)

        assert path.exists()
        data = json.loads(path.read_text())
        assert data["version"] == SCHEMA_VERSION
        assert "src/main.py" in data["modules"]

    def test_save_updates_timestamp(self, temp_dir):
        """save_registry updates last_updated."""
        reg = Registry()
        path = temp_dir / "registry.json"

        save_registry(reg, path)

        assert reg.last_updated is not None
        # Should be an ISO timestamp
        assert "T" in reg.last_updated

    def test_save_records_commit(self, temp_dir):
        """save_registry records commit if provided."""
        reg = Registry()
        path = temp_dir / "registry.json"

        save_registry(reg, path, commit="abc123")

        assert reg.current_commit == "abc123"
        data = json.loads(path.read_text())
        assert data["current_commit"] == "abc123"

    def test_save_without_commit(self, temp_dir):
        """save_registry preserves existing commit when not provided."""
        reg = Registry(current_commit="original123")
        path = temp_dir / "registry.json"

        save_registry(reg, path)

        assert reg.current_commit == "original123"

    def test_save_overwrites_file(self, temp_dir):
        """save_registry overwrites existing file."""
        path = temp_dir / "registry.json"
        path.write_text('{"old": "data"}')

        reg = Registry()
        reg.set(ModuleStatus(path="new.py"))
        save_registry(reg, path)

        data = json.loads(path.read_text())
        assert "old" not in data
        assert "new.py" in data["modules"]

    def test_save_load_roundtrip(self, temp_dir):
        """Saved registry can be loaded correctly."""
        original = Registry()
        original.set(ModuleStatus(path="a.py", status="reviewed", lines=100))
        original.set(
            ModuleStatus(path="b.py", status="in_progress", reviewers=["@reviewer"])
        )
        path = temp_dir / "registry.json"

        save_registry(original, path, commit="abc123")
        loaded = load_registry(path)

        assert len(loaded) == 2
        assert loaded.get("a.py").status == "reviewed"
        assert loaded.get("b.py").reviewers == ["@reviewer"]
        assert loaded.current_commit == "abc123"
