"""Tests for glreview.claude_context module."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from glreview.claude_context import (
    ModuleContext,
    ProjectContext,
    format_module_context_for_prompt,
    get_default_context_path,
    load_context,
    save_context,
)


class TestModuleContext:
    """Tests for ModuleContext dataclass."""

    def test_default_values(self):
        """Creates with default values."""
        ctx = ModuleContext(path="src/main.py")
        assert ctx.path == "src/main.py"
        assert ctx.purpose == ""
        assert ctx.test_files == []
        assert ctx.imports_from == []
        assert ctx.imported_by == []
        assert ctx.related_files == []
        assert ctx.notes == ""

    def test_custom_values(self):
        """Creates with custom values."""
        ctx = ModuleContext(
            path="src/main.py",
            purpose="Main entry point",
            test_files=["tests/test_main.py"],
            imports_from=["src/utils.py"],
            imported_by=["src/cli.py"],
            related_files=["src/config.py"],
            notes="Critical module",
        )
        assert ctx.purpose == "Main entry point"
        assert ctx.test_files == ["tests/test_main.py"]
        assert ctx.imports_from == ["src/utils.py"]
        assert ctx.imported_by == ["src/cli.py"]
        assert ctx.related_files == ["src/config.py"]
        assert ctx.notes == "Critical module"

    def test_to_dict(self):
        """Converts to dictionary."""
        ctx = ModuleContext(
            path="src/main.py",
            purpose="Main entry",
            test_files=["test_main.py"],
            imports_from=["utils.py"],
            imported_by=["cli.py"],
            related_files=["config.py"],
            notes="Important",
        )
        d = ctx.to_dict()

        assert d["purpose"] == "Main entry"
        assert d["test_files"] == ["test_main.py"]
        assert d["imports_from"] == ["utils.py"]
        assert d["imported_by"] == ["cli.py"]
        assert d["related_files"] == ["config.py"]
        assert d["notes"] == "Important"
        # path is not included in to_dict
        assert "path" not in d

    def test_from_dict(self):
        """Creates from dictionary."""
        data = {
            "purpose": "Test purpose",
            "test_files": ["test.py"],
            "imports_from": ["a.py"],
            "imported_by": ["b.py"],
            "related_files": ["c.py"],
            "notes": "Note",
        }
        ctx = ModuleContext.from_dict("src/main.py", data)

        assert ctx.path == "src/main.py"
        assert ctx.purpose == "Test purpose"
        assert ctx.test_files == ["test.py"]
        assert ctx.imports_from == ["a.py"]
        assert ctx.imported_by == ["b.py"]
        assert ctx.related_files == ["c.py"]
        assert ctx.notes == "Note"

    def test_from_dict_missing_keys(self):
        """Handles missing keys with defaults."""
        data = {"purpose": "Partial"}
        ctx = ModuleContext.from_dict("src/main.py", data)

        assert ctx.path == "src/main.py"
        assert ctx.purpose == "Partial"
        assert ctx.test_files == []
        assert ctx.imports_from == []
        assert ctx.imported_by == []
        assert ctx.related_files == []
        assert ctx.notes == ""

    def test_from_dict_empty(self):
        """Handles empty dictionary."""
        ctx = ModuleContext.from_dict("src/main.py", {})
        assert ctx.path == "src/main.py"
        assert ctx.purpose == ""


class TestProjectContext:
    """Tests for ProjectContext dataclass."""

    def test_default_values(self):
        """Creates with default values."""
        ctx = ProjectContext()
        assert ctx.version == "1"
        assert ctx.generated_at == ""
        assert ctx.generated_commit == ""
        assert ctx.description == ""
        assert ctx.conventions == ""
        assert ctx.architecture == ""
        assert ctx.test_patterns == ""
        assert ctx.modules == {}

    def test_custom_values(self):
        """Creates with custom values."""
        ctx = ProjectContext(
            version="2",
            generated_at="2024-01-01T00:00:00Z",
            generated_commit="abc123",
            description="Test project",
            conventions="Use black",
            architecture="MVC",
            test_patterns="pytest",
        )
        assert ctx.version == "2"
        assert ctx.generated_at == "2024-01-01T00:00:00Z"
        assert ctx.generated_commit == "abc123"
        assert ctx.description == "Test project"
        assert ctx.conventions == "Use black"
        assert ctx.architecture == "MVC"
        assert ctx.test_patterns == "pytest"

    def test_to_dict(self):
        """Converts to dictionary."""
        mod_ctx = ModuleContext(path="src/main.py", purpose="Entry")
        ctx = ProjectContext(
            version="1",
            generated_at="2024-01-01",
            generated_commit="abc",
            description="Desc",
            conventions="Conv",
            architecture="Arch",
            test_patterns="Test",
            modules={"src/main.py": mod_ctx},
        )
        d = ctx.to_dict()

        assert d["version"] == "1"
        assert d["generated_at"] == "2024-01-01"
        assert d["generated_commit"] == "abc"
        assert d["project"]["description"] == "Desc"
        assert d["project"]["conventions"] == "Conv"
        assert d["project"]["architecture"] == "Arch"
        assert d["project"]["test_patterns"] == "Test"
        assert "src/main.py" in d["modules"]
        assert d["modules"]["src/main.py"]["purpose"] == "Entry"

    def test_from_dict(self):
        """Creates from dictionary."""
        data = {
            "version": "2",
            "generated_at": "2024-01-01",
            "generated_commit": "def456",
            "project": {
                "description": "My project",
                "conventions": "PEP8",
                "architecture": "Layered",
                "test_patterns": "unittest",
            },
            "modules": {
                "src/main.py": {
                    "purpose": "Main module",
                    "test_files": ["test_main.py"],
                }
            },
        }
        ctx = ProjectContext.from_dict(data)

        assert ctx.version == "2"
        assert ctx.generated_at == "2024-01-01"
        assert ctx.generated_commit == "def456"
        assert ctx.description == "My project"
        assert ctx.conventions == "PEP8"
        assert ctx.architecture == "Layered"
        assert ctx.test_patterns == "unittest"
        assert "src/main.py" in ctx.modules
        assert ctx.modules["src/main.py"].purpose == "Main module"

    def test_from_dict_missing_keys(self):
        """Handles missing keys with defaults."""
        data = {"version": "1"}
        ctx = ProjectContext.from_dict(data)

        assert ctx.version == "1"
        assert ctx.description == ""
        assert ctx.modules == {}

    def test_from_dict_empty(self):
        """Handles empty dictionary."""
        ctx = ProjectContext.from_dict({})
        assert ctx.version == "1"
        assert ctx.modules == {}

    def test_get_module(self):
        """Gets module context by path."""
        mod_ctx = ModuleContext(path="src/main.py", purpose="Entry")
        ctx = ProjectContext(modules={"src/main.py": mod_ctx})

        result = ctx.get_module("src/main.py")
        assert result is not None
        assert result.purpose == "Entry"

    def test_get_module_not_found(self):
        """Returns None for missing module."""
        ctx = ProjectContext()
        result = ctx.get_module("nonexistent.py")
        assert result is None

    def test_set_module(self):
        """Sets module context."""
        ctx = ProjectContext()
        mod_ctx = ModuleContext(path="src/new.py", purpose="New module")

        ctx.set_module(mod_ctx)

        assert "src/new.py" in ctx.modules
        assert ctx.modules["src/new.py"].purpose == "New module"


class TestLoadContext:
    """Tests for load_context function."""

    def test_loads_existing_file(self, temp_dir):
        """Loads context from existing file."""
        context_path = temp_dir / "context.json"
        data = {
            "version": "1",
            "generated_at": "2024-01-01",
            "generated_commit": "abc123",
            "project": {
                "description": "Test project",
            },
            "modules": {},
        }
        context_path.write_text(json.dumps(data))

        ctx = load_context(context_path)

        assert ctx.generated_commit == "abc123"
        assert ctx.description == "Test project"

    def test_returns_empty_for_missing_file(self, temp_dir):
        """Returns empty context when file doesn't exist."""
        context_path = temp_dir / "nonexistent.json"

        ctx = load_context(context_path)

        assert ctx.version == "1"
        assert ctx.generated_commit == ""
        assert ctx.modules == {}


class TestSaveContext:
    """Tests for save_context function."""

    def test_saves_context(self, temp_dir):
        """Saves context to file."""
        context_path = temp_dir / "context.json"
        ctx = ProjectContext(
            description="Test",
            conventions="PEP8",
        )

        save_context(ctx, context_path, commit="abc123")

        assert context_path.exists()
        data = json.loads(context_path.read_text())
        assert data["generated_commit"] == "abc123"
        assert data["project"]["description"] == "Test"
        assert data["generated_at"]  # Should be set

    def test_saves_without_commit(self, temp_dir):
        """Saves context without commit."""
        context_path = temp_dir / "context.json"
        ctx = ProjectContext(description="Test")

        save_context(ctx, context_path)

        data = json.loads(context_path.read_text())
        assert data["generated_commit"] == ""
        assert data["generated_at"]  # Should still be set

    def test_creates_parent_directories(self, temp_dir):
        """Creates parent directories if needed."""
        context_path = temp_dir / "subdir" / "nested" / "context.json"
        ctx = ProjectContext()

        save_context(ctx, context_path)

        assert context_path.exists()


class TestGetDefaultContextPath:
    """Tests for get_default_context_path function."""

    def test_returns_correct_path(self, temp_dir):
        """Returns .glreview/context.json path."""
        path = get_default_context_path(temp_dir)

        assert path == temp_dir / ".glreview" / "context.json"


class TestFormatModuleContextForPrompt:
    """Tests for format_module_context_for_prompt function."""

    def test_empty_context(self):
        """Returns empty string for no context."""
        result = format_module_context_for_prompt(None, None)
        assert result == ""

    def test_project_conventions_only(self):
        """Formats project conventions."""
        project_ctx = ProjectContext(conventions="Use black formatting")

        result = format_module_context_for_prompt(None, project_ctx)

        assert "## Project Conventions" in result
        assert "Use black formatting" in result

    def test_project_architecture_only(self):
        """Formats project architecture."""
        project_ctx = ProjectContext(architecture="MVC pattern")

        result = format_module_context_for_prompt(None, project_ctx)

        assert "## Architecture Notes" in result
        assert "MVC pattern" in result

    def test_module_purpose(self):
        """Formats module purpose."""
        module_ctx = ModuleContext(path="main.py", purpose="Main entry point")

        result = format_module_context_for_prompt(module_ctx, None)

        assert "## Module Purpose" in result
        assert "Main entry point" in result

    def test_module_test_files(self):
        """Formats test files list."""
        module_ctx = ModuleContext(
            path="main.py",
            test_files=["test_main.py", "test_integration.py"],
        )

        result = format_module_context_for_prompt(module_ctx, None)

        assert "## Associated Test Files" in result
        assert "`test_main.py`" in result
        assert "`test_integration.py`" in result

    def test_module_imports_from(self):
        """Formats dependencies."""
        module_ctx = ModuleContext(
            path="main.py",
            imports_from=["utils.py", "config.py"],
        )

        result = format_module_context_for_prompt(module_ctx, None)

        assert "## Dependencies (imports from)" in result
        assert "`utils.py`" in result
        assert "`config.py`" in result

    def test_module_imported_by(self):
        """Formats dependents."""
        module_ctx = ModuleContext(
            path="utils.py",
            imported_by=["main.py", "cli.py"],
        )

        result = format_module_context_for_prompt(module_ctx, None)

        assert "## Dependents (imported by)" in result
        assert "`main.py`" in result
        assert "`cli.py`" in result

    def test_module_notes(self):
        """Formats review notes."""
        module_ctx = ModuleContext(
            path="main.py",
            notes="Pay attention to error handling",
        )

        result = format_module_context_for_prompt(module_ctx, None)

        assert "## Review Notes" in result
        assert "Pay attention to error handling" in result

    def test_full_context(self):
        """Formats full context with all fields."""
        project_ctx = ProjectContext(
            conventions="PEP8 style",
            architecture="Layered architecture",
        )
        module_ctx = ModuleContext(
            path="main.py",
            purpose="Entry point",
            test_files=["test_main.py"],
            imports_from=["utils.py"],
            imported_by=["cli.py"],
            notes="Critical",
        )

        result = format_module_context_for_prompt(module_ctx, project_ctx)

        assert "## Project Conventions" in result
        assert "## Architecture Notes" in result
        assert "## Module Purpose" in result
        assert "## Associated Test Files" in result
        assert "## Dependencies (imports from)" in result
        assert "## Dependents (imported by)" in result
        assert "## Review Notes" in result

    def test_empty_fields_excluded(self):
        """Empty fields are not included."""
        project_ctx = ProjectContext(conventions="")  # Empty
        module_ctx = ModuleContext(path="main.py", purpose="")  # Empty

        result = format_module_context_for_prompt(module_ctx, project_ctx)

        assert result == ""
